|
786 | 786 | "This approach can provide significant speedup and memory savings in certain cases, but should only be used for good reasons, since it can cause confusion for any code expecting instances to be independent as they have been declared." |
787 | 787 | ] |
788 | 788 | }, |
| 789 | + { |
| 790 | + "cell_type": "markdown", |
| 791 | + "id": "14a20588", |
| 792 | + "metadata": {}, |
| 793 | + "source": [ |
| 794 | + "## Parameterized Abstract Base Class\n", |
| 795 | + "\n", |
| 796 | + "Param supports two ways of declaring a class as an abstract base class (ABC), which is a good approach to define an interface subclasses should implement:\n", |
| 797 | + "\n", |
| 798 | + "- Added in version 2.3.0, an abstract Parameterized class can be created by inheriting from `ParameterizedABC`, which is equivalent as inheriting from `ABC` from the Python [abc](https://docs.python.org/3/library/abc.html) module.\n", |
| 799 | + "- A Parameterized class can be annotated with the class attribute `__abstract` set to `True` to declare it as abstract.\n", |
| 800 | + "\n", |
| 801 | + "We recommend adopting the first approach that is more generic and powerful. The second approach is specific to Param (`inspect.isabstract(class)` won't return `True` for example) and preserved for compatibility reasons.\n", |
| 802 | + "\n", |
| 803 | + ":::{warning}\n", |
| 804 | + "It is recommended not to mix the two approaches.\n", |
| 805 | + ":::\n", |
| 806 | + "\n", |
| 807 | + "Let's start with an example using the first approach, declaring the `ProcessorABC` interface with a `run` method subclasses must implement." |
| 808 | + ] |
| 809 | + }, |
| 810 | + { |
| 811 | + "cell_type": "code", |
| 812 | + "execution_count": null, |
| 813 | + "id": "94ad7fb9-1719-45a2-96a3-38f1c2b1f97c", |
| 814 | + "metadata": {}, |
| 815 | + "outputs": [], |
| 816 | + "source": [ |
| 817 | + "import abc\n", |
| 818 | + "\n", |
| 819 | + "class ProcessorABC(param.ParameterizedABC):\n", |
| 820 | + " x = param.Number()\n", |
| 821 | + " y = param.Number()\n", |
| 822 | + "\n", |
| 823 | + " @abc.abstractmethod\n", |
| 824 | + " def run(self): pass" |
| 825 | + ] |
| 826 | + }, |
| 827 | + { |
| 828 | + "cell_type": "markdown", |
| 829 | + "id": "323f7bb4-f153-49ed-bf1d-3429d31cf865", |
| 830 | + "metadata": {}, |
| 831 | + "source": [ |
| 832 | + "Subclasses that do not implement the interface cannot be instantiated, this is the standard behavior of a Python ABC." |
| 833 | + ] |
| 834 | + }, |
| 835 | + { |
| 836 | + "cell_type": "code", |
| 837 | + "execution_count": null, |
| 838 | + "id": "0db17ea0-ed36-40f9-9e7a-10126e1b3ef3", |
| 839 | + "metadata": {}, |
| 840 | + "outputs": [], |
| 841 | + "source": [ |
| 842 | + "class BadProcessor(ProcessorABC):\n", |
| 843 | + " def run_not_implemented(self): pass\n", |
| 844 | + "\n", |
| 845 | + "with param.exceptions_summarized():\n", |
| 846 | + " BadProcessor()" |
| 847 | + ] |
| 848 | + }, |
| 849 | + { |
| 850 | + "cell_type": "markdown", |
| 851 | + "id": "d4ab49c6-d1a8-4064-9d6f-dd2c6a1b61a5", |
| 852 | + "metadata": {}, |
| 853 | + "source": [ |
| 854 | + "A valid subclass can be instantiated and used." |
| 855 | + ] |
| 856 | + }, |
| 857 | + { |
| 858 | + "cell_type": "code", |
| 859 | + "execution_count": null, |
| 860 | + "id": "717f781a-4c2f-446b-b8f1-25a0d95993e0", |
| 861 | + "metadata": {}, |
| 862 | + "outputs": [], |
| 863 | + "source": [ |
| 864 | + "class GoodProcessor(ProcessorABC):\n", |
| 865 | + " def run(self):\n", |
| 866 | + " return self.x * self.y\n", |
| 867 | + "\n", |
| 868 | + "GoodProcessor(x=2, y=4).run()" |
| 869 | + ] |
| 870 | + }, |
| 871 | + { |
| 872 | + "cell_type": "markdown", |
| 873 | + "id": "7a2d952c-47bc-43cf-bfe0-fc011c02973a", |
| 874 | + "metadata": {}, |
| 875 | + "source": [ |
| 876 | + "Let's see now how using the second approach differs from the first one by creating a new base class, this time a simple `Parameterized` subclass with a class attribute `__abstract` set to `True` " |
| 877 | + ] |
| 878 | + }, |
| 879 | + { |
| 880 | + "cell_type": "code", |
| 881 | + "execution_count": null, |
| 882 | + "id": "b757bc37-0641-45da-9e7f-7a1cae97ea99", |
| 883 | + "metadata": {}, |
| 884 | + "outputs": [], |
| 885 | + "source": [ |
| 886 | + "class ProcessorBase(param.Parameterized):\n", |
| 887 | + " __abstract = True\n", |
| 888 | + "\n", |
| 889 | + " x = param.Number()\n", |
| 890 | + " y = param.Number()\n", |
| 891 | + "\n", |
| 892 | + " def run(self): raise NotImplementedError(\"Subclasses must implement the run method\")" |
| 893 | + ] |
| 894 | + }, |
| 895 | + { |
| 896 | + "cell_type": "markdown", |
| 897 | + "id": "72ca4f32-6922-4ca4-80c4-0a9be0068877", |
| 898 | + "metadata": {}, |
| 899 | + "source": [ |
| 900 | + "Param does not validate that subclasses correctly implement the interface. In this example, calling the non-implemented method will execute the method inherited from the base class." |
| 901 | + ] |
| 902 | + }, |
| 903 | + { |
| 904 | + "cell_type": "code", |
| 905 | + "execution_count": null, |
| 906 | + "id": "935b9889-2296-43f3-af70-f2d9da512cf6", |
| 907 | + "metadata": {}, |
| 908 | + "outputs": [], |
| 909 | + "source": [ |
| 910 | + "class BadProcessor(ProcessorBase):\n", |
| 911 | + " def run_not_implemented(self): pass\n", |
| 912 | + "\n", |
| 913 | + "bp = BadProcessor()\n", |
| 914 | + "with param.exceptions_summarized():\n", |
| 915 | + " bp.run()" |
| 916 | + ] |
| 917 | + }, |
| 918 | + { |
| 919 | + "cell_type": "code", |
| 920 | + "execution_count": null, |
| 921 | + "id": "f60b383e-a735-453f-b706-513ebb909d41", |
| 922 | + "metadata": {}, |
| 923 | + "outputs": [], |
| 924 | + "source": [ |
| 925 | + "class GoodProcessor(ProcessorBase):\n", |
| 926 | + " def run(self):\n", |
| 927 | + " return self.x * self.y\n", |
| 928 | + "\n", |
| 929 | + "GoodProcessor(x=2, y=4).run()" |
| 930 | + ] |
| 931 | + }, |
| 932 | + { |
| 933 | + "cell_type": "markdown", |
| 934 | + "id": "ccf36d91-c1d3-4534-949f-bf0d1863c163", |
| 935 | + "metadata": {}, |
| 936 | + "source": [ |
| 937 | + "Parameterizes classes have an `abstract` property that returns `True` whenever a class is declared as abstract in the two supported approaches." |
| 938 | + ] |
| 939 | + }, |
| 940 | + { |
| 941 | + "cell_type": "code", |
| 942 | + "execution_count": null, |
| 943 | + "id": "16125aec", |
| 944 | + "metadata": {}, |
| 945 | + "outputs": [], |
| 946 | + "source": [ |
| 947 | + "ProcessorABC.abstract, ProcessorBase.abstract, GoodProcessor.abstract" |
| 948 | + ] |
| 949 | + }, |
| 950 | + { |
| 951 | + "cell_type": "markdown", |
| 952 | + "id": "e9bbc4a2-3782-4568-99d9-dbdb1b6e0fe2", |
| 953 | + "metadata": {}, |
| 954 | + "source": [ |
| 955 | + "The `descendents` function returns a list of all the descendents of a class including the parent class. It supports a `concrete` keyword that can be set to `True` to filter out abstract classes. Note that with the first approach, `BadProcessor` isn't returned as it doesn't implement the interface of the abstract class." |
| 956 | + ] |
| 957 | + }, |
| 958 | + { |
| 959 | + "cell_type": "code", |
| 960 | + "execution_count": null, |
| 961 | + "id": "933b1628", |
| 962 | + "metadata": {}, |
| 963 | + "outputs": [], |
| 964 | + "source": [ |
| 965 | + "param.descendents(ProcessorABC, concrete=True)" |
| 966 | + ] |
| 967 | + }, |
| 968 | + { |
| 969 | + "cell_type": "code", |
| 970 | + "execution_count": null, |
| 971 | + "id": "3b59e470", |
| 972 | + "metadata": {}, |
| 973 | + "outputs": [], |
| 974 | + "source": [ |
| 975 | + "param.descendents(ProcessorBase, concrete=True)" |
| 976 | + ] |
| 977 | + }, |
789 | 978 | { |
790 | 979 | "cell_type": "markdown", |
791 | 980 | "id": "678b7a0e", |
|
0 commit comments