5

Given the following code:

class DTC:
    def __init__(self):
        self.__root = None

    def unique(self,Y):
        d = {}
        for i in Y:
            if i not in d:
                d[i]=1
            else:
                d[i]+=1
        return d

    def ent(self,Y):
        freq = self.__count_unique(Y)
        ent_ = 0
        total = len(Y)
        for i in freq:
            p = freq[i]/total
            entropy_ += (-p)*math.log2(p)
        return ent_

The above will run if it is place in a single cell in Jupyter Notebook. However, how can I make the class code work if I want it to be split into multiple cells like this:

Cell 1

class DTC:
    def __init__(self):
        self.__root = None

Cell 2

    def unique(self,Y):
        d = {}
        for i in Y:
            if i not in d:
                d[i]=1
            else:
                d[i]+=1
        return d

Cell 3

    def ent(self,Y):
        freq = self.__unique(Y)
        ent_ = 0
        total = len(Y)
        for i in freq:
            p = freq[i]/total
            ent_ += (-p)*math.log2(p)
        return ent_
anurag
  • 1,715
  • 1
  • 8
  • 28
user3118602
  • 553
  • 5
  • 19

3 Answers3

3

There are two methods to split a class definition over multiple cells in Jupyter Notebooks

Method 1

Doing it the naive way (exploiting inheritance and over-riding):

Cell-1

class DTC:
    def __init__(self):
        self.__root = None

Cell-2

class DTC(DTC):
    def unique(self,Y):
        d = {}
        for i in Y:
            if i not in d:
                d[i]=1
            else:
                d[i]+=1
        return d

Cell-3

class DTC(DTC):
    def ent(self,Y):
        freq = self.__count_unique(Y)
        ent_ = 0
        total = len(Y)
        for i in freq:
            p = freq[i]/total
            entropy_ += (-p)*math.log2(p)
        return ent_

The thing to note is that this actually creates a hierarchy of classes internally:

import inspect
inspect.getmro(DTC)
# outputs: (__main__.DTC, __main__.DTC, __main__.DTC, object)

If you do not plan to stretch over too many cells, you can use this method.

Method 2

Use the package jdc; more details/docs for jdc

Cell 1

import jdc        # jupyter dynamic classes

class DTC:
    def __init__(self):
        self.__root = None

Cell 2

%%add_to DTC
def unique(self,Y):
    d = {}
    for i in Y:
        if i not in d:
            d[i]=1
        else:
            d[i]+=1
    return d

Cell 3

%%add_to DTC
def ent(self,Y):
    freq = self.__count_unique(Y)
    ent_ = 0
    total = len(Y)
    for i in freq:
        p = freq[i]/total
        entropy_ += (-p)*math.log2(p)
    return ent_

This time no hierarchies are formed:

import inspect
inspect.getmro(DTC)
#output: (__main__.DTC, object)
anurag
  • 1,715
  • 1
  • 8
  • 28
  • The above has been reproduced from [this github issue](https://github.com/jupyter/notebook/issues/1243) from Jupyter's official repository. – anurag Feb 12 '21 at 17:37
1

A python-only solution:

class OutsourceMethods:
    @classmethod
    def method(cls, f):
        setattr(cls, f.__name__, f)
        

Used as:

class A(SuperA, OutsourceMethods):
    def __init__(self):
        self.x = 10


@A.method
def bar(self, y):
    print(self.x, y)

a = A()

a.bar(20)

> 10 20

b = A()
b.x = 3
b.bar() 

> 3 20

It's not 100% equivalent, but I haven't noticed a different so far.

Nearoo
  • 4,454
  • 3
  • 28
  • 39
0

Use patch (or patch_to) decorators from the fastcore basics module.

For example:

from fastcore.basics import patch

Cell 1:

class DTC:
    def __init__(self):
        self.__root = None

Cell 2:

@patch
def unique(self: DTC, Y):
    d = {}
    for i in Y:
        if i not in d:
            d[i]=1
        else:
            d[i]+=1
    return d

For benefits over monkey-patching, see here: https://stackoverflow.com/a/75873731/1544154

Elijas Dapšauskas
  • 909
  • 10
  • 25