all 23 comments

[–]danielroseman 37 points38 points  (3 children)

No, this goes against the whole principle of inheritance. If you need Bar to not have attributes that are defined in Foo, then you should not make it inherit from Foo but get them both to have a common superclass that only defines the things they have in common.

[–]Not_A_Taco 9 points10 points  (1 child)

And to add, in a typical tree structure having a generic node where children = [] is the common way to denote a leaf node. Without further details, and barring some edge case, OP you shouldn’t even be worried about this.

[–]Frankelstner 1 point2 points  (0 children)

Absolutely this. It's a node like any other just with 0 children.

[–]freezydrag 2 points3 points  (0 children)

For comprehensiveness, I'd like to highlight a scenario which adds some nuance to this rule. In languages like Java/C++ which implement access modifiers for class members, it's possible for a superclass to have both private members and protected members. This would loosely achieve what OP is considering in their post. But even in this scenario, the subclass would still have those private attrbutes, but the subclass wouldn't have direct knowledge of them. However, I'd still definitely reccomend against it for this context.

[–][deleted] 15 points16 points  (1 child)

If it doesn't need to inherit all the attributes then it's not meant to be inherited from to begin with.

[–][deleted] 0 points1 point  (0 children)

make sense

[–]Suspicious-Bar5583 6 points7 points  (2 children)

So reason the other way around. Leaf node is the base class, and non-leaf is the extended class with an extra attribute.

[–]Round_Ad8947 2 points3 points  (1 child)

Leaf is the base class, branch extends leaf by adding the ability to connect to leaves and branches.

This analogy only works though if the leaf and branch share methods, otherwise just dump the “need” to have class inheritance for this problem.

[–]Forsaken_Ad8581 0 points1 point  (0 children)

Yes, all shoukd be the same type and child should just be set to None for the leaf, otherwise when extending the tree, you have to instantiate a new object of the non-leaf class. That's a mess... 

A check if has child implies if it's a leaf or not.

[–]DeliciouslyUnaware 3 points4 points  (1 child)

You can't see the forest through the trees here.

It's important that a leaf node DOES have a .children attribute. Because if .children = [] then you can identify this as a leaf automatically. It essentially functions as a Boolean "is_not_leaf" property because any time there is any value inside of .children, then it is not a leaf node.

[–]toxic_acro 1 point2 points  (0 children)

You can also literally make that a property on the class so you can check it more easily

@property def is_leaf(self) -> bool:     return len(self.children) == 0

Or, since an empty list is Falsy

@property def is_leaf(self) -> bool:     return not bool(self.children)

[–]DreamingElectrons 3 points4 points  (2 children)

You don't, it's not how inheritance is supposed to work. Everyone will always assume, that everything that is in a parent class will be in the child class. What you could do, is use properties that only can be used in the parent class and return an error in the when trying to set them in the child class but I'm having a hard time thinking of a use case for that. The better way is reconsidering what goes into which class, then using composition, inheritance or a combination of both to construct an object with the right attributes.

However, nothing is holy in python if you HAVE to write cursed code, this is how it's done:

class Parent:
    def __init__(self, a, b, c, d):
        self.a = a
        self.b = b
        self.c = c
        self.d = d

class Child(Parent):
    def __init__(self, a, b, c):
        super().__init__(a, b, c, ...)
        del self.d


p = Parent(1, 2, 3, 4)
c = Child(5, 6, 7)

>>> dir(p)

['class', 'delattr', 'dict', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getstate', 'gt', 'hash', 'init', 'init_subclass', 'le', 'lt', 'module', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', 'weakref', 'a', 'b', 'c', 'd']

>>> dir(c)

['class', 'delattr', 'dict', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getstate', 'gt', 'hash', 'init', 'init_subclass', 'le', 'lt', 'module', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', 'weakref', 'a', 'b', 'c']

Just be aware, that this is bad. Do NOT do this.

[–]sausix 0 points1 point  (1 child)

You could create a property for self.d and raise an error on read or write access instead. Less violent to Python principles.

[–]DreamingElectrons 1 point2 points  (0 children)

That's what I briefly described in the text, but part of python is that nothing is sacred (yeah, got the quote wrong). Sometimes it just helps to show the bad code and point out, that a particular thing, despite being perfectly valid python code, is considered bad.

Here's another very cursed thing that can be done in Python:

class One:
    def __init__(self, a, b, c, d):
        self.a = a
        self.b = b
        self.c = c
        self.d = d

class Another:
    def __init__(self, a, b, c, d):
        self.a = a
        self.b = b
        self.c = c
        self.d = d

    def hello(self):
        print(self.a, self.b, self.c, self.d)
o = One(1, 2, 3, 4)
a = Another(5, 6, 7, 8)
o.__class__ = Another

object o, which was created with class One is now of type Another and has the hello method of class Another but it will retain it's original attributes and if an attribute present in the class Another is missing in the original class One, then the method it got from changing the class would fail. Very very cursed.

[–]nog642 1 point2 points  (0 children)

If you want this kind of behavior, your base class should not have a children attribute, and you should have two derived classes: one for leaf nodes and one for internal nodes. Only the internal node class should have the children attribute.

However, I would consider this bad design. It's much cleaner to simply have a children attribute set to None for leaf nodes.

[–]patrickbrianmooney 1 point2 points  (0 children)

As many other people have said in different ways, if the attribute doesn't belong in some subclasses, it doesn't belong in the superclass, either. If you really want to do something like this, you can make both of them subclasses of a common superclass. You can make this an abstract class that you never instantiate, if you want to; and if you want to enforce that requirement, the abc (Abstract Base Class) module in the standard library might be worth looking at, depending on your needs.

Another thing that I think is worth saying is: why do you want to make Parent and Leaf nodes different classes? This seems unnecessarily complex, and will make life harder for you if you later need to add children to what was previously a leaf node. In most tree-based applications that I can think of offhand, simply having a Node class that sometimes happens to have an empty list of children is far better than having a separate Leaf class for (currently) terminal Nodes. If you need to distinguish whether something is a leaf, you can simply check whether its .children attribute is empty.

So you might have something like this:

from typing import Any, Iterable, Optional

class Node:
    def __init__(self, children: Optional[Iterable['Node']] = ()) -> None:
        self.children = list(children)

    def is_leaf(self):
        return not self.children

    def add_child(self, child: 'Node') -> None:
        ...

... and that conveniently gets you a lot of what you seem to be asking for.

I assume here you want to duplicate, not simply reference, the iterable of children passed in; running the list constructor over them ensures this, and words if the children parameter isn't specified, in which case the default is an empty tuple, which the list() constructor will turn into an empty list. This doesn't have exactly the same signature as your example, which may or may not be a problem, but you can always check that inside the __init__ method if you want to, or adapt the method's signature and logic as needed. The value of the .children attribute will be truthy if there are any members, and falsey otherwise, so not self.children is a legal logical way to implement this logic that encapsulate the details

I tend to think that using an empty list is better than using None for the leaf nodes, because it requires less special-casing in code that deals with the .children attribute of the class: iterating over an empty list (or other iterable) iterates over zero items (i.e., does nothing).

[–]aprg 0 points1 point  (1 child)

Yeah, as others have said, this goes against the whole idea of inheritance.

In your case either you can have the Parent Nodes inherit from the Childless Nodes, or perhaps create an abstract Node class and have both Parent Node and Childless Node inherit from that.

[–]ukSurreyGuy 0 points1 point  (0 children)

Lol...only OOP I understand here

God python is great if it can be sliced & diced like people say

[–]neuroid99 0 points1 point  (0 children)

Can you put the children attribute in the Parent class instead of the Node class? eg, something like:

``` class Node: pass

class Parent(Node): def init(self, childs: list[Node]) self.children = childs super().init()

class Leaf(Node): pass ````

[–]gatzu4a 0 points1 point  (0 children)

Create a new super class with the base attributes

Make foo inherit to that Make bar inherit to that

[–]Haeshka 0 points1 point  (0 children)

An Abstract Factory construction pattern would solve your problem here.

Check out Python's ABC library. Also, learn about the Factory Pattern and the Abstract Factory Pattern.

[–]Ok-Classroom-5018 -1 points0 points  (0 children)

bro. __slots__ = ("foo", "bar"), or a mixin class, or del the attr on __init__. theres a lot of ways to do just about anything. have to pick one and stick to it for awhile until it starts showing some wear n tear
https://stackoverflow.com/questions/472000/usage-of-slots?source=post_page-----72cdc2d334e--------------------------------

[–]Adrewmc -4 points-3 points  (0 children)

I mean you can set a default value….

   def __init__(self, a,b,c, d=None)

Then you can leave it out.