0

I would like to choose a subclass at instantiation to select specific attributes from certain classes. I've worked out the following:

Code

class Foo:
    a = "foo"

    def __init__(self, dtype=Bar):
            self.__class__ = dtype


class Bar:
    b = "bar"


class Baz(Bar):
    c = "baz"

Demo

Foo(dtype=Bar).b
# 'bar'

Foo(dtype=Baz).b
# 'bar'

Foo(dtype=Baz).c
# 'baz'

This gives the desired result, selecting specific attributes from Bar while optionally extending features with Baz. Unlike subclassing however, we have no access to Foo's attributes.

Foo(dtype=Baz).a
# AttributeError: 'Baz' object has no attribute 'a'

There are occasions when not all attributes are desired, so subclassing Foo(Baz) is not preferred.

What's the idiomatic analog to accomplish this in Python?

pylang
  • 40,867
  • 14
  • 129
  • 121
  • 2
    Why can't you just use inheritance? You could dynamically pick the subclass like `var = Bar; var()`. – Kent Shikama Dec 07 '19 at 00:14
  • 3
    This sounds like an [XY problem](https://en.wikipedia.org/wiki/XY_problem). I don't know that doing this would ever be idiomatic. – luther Dec 07 '19 at 00:18
  • @KentShikama Choosing `var = Bar()` still excludes attributes in `Foo`. – pylang Dec 07 '19 at 00:54
  • Per https://docs.python.org/3/reference/datamodel.html#object.__new__ , if `__new__` returns an instance (including subclasses), `__init__` will be called. But this is all performed by `type.__call__`; see also https://stackoverflow.com/questions/6966772/using-the-call-method-of-a-metaclass-instead-of-new – o11c Dec 07 '19 at 02:03
  • That said, `__new__` returning a different type should be quite rare; I've only done it in FFI-related code to map `NULL` to `None`. – o11c Dec 07 '19 at 02:05
  • @pylang Can you please elaborate? `var = Bar; var().a` outputs "foo" as expected. – Kent Shikama Dec 07 '19 at 02:15
  • Note it isn't `Bar() but Bar`; you're dynamically choosing the class to instantiate. – Kent Shikama Dec 07 '19 at 02:16
  • I've posted an answer below to clarify what I am saying. – Kent Shikama Dec 07 '19 at 02:23

2 Answers2

1

Why create an instance of Foo in the first place if you really want an instance of Bar or Baz? Instead, make Foo a factory for instance of Bar and Baz.

class Bar:
    b = "bar"

class Baz(Bar):
    c = "baz"

class Foo:
    a = "foo"

    def __new__(cls, dtype=Bar):
        return dtype()
chepner
  • 497,756
  • 71
  • 530
  • 681
0

As I mentioned in the comments, just use plain inheritance.

class Foo:
    a = "foo"

class Bar(Foo):
    b = "bar"

class Baz(Foo):
    c = "baz"

# Example use
import random
class_to_instantiate = Bar if random.choice([True, False]) else Baz
print(class_to_instantiate().a)

Outputs

foo
Kent Shikama
  • 3,910
  • 3
  • 22
  • 55