0

Contiuing from this post: Dynamically creating a class from file, type(name, bases, dict) can be used to dynamically create a class with name name, with base classes bases, and attributes dict.

I have classes such as:

class City:
    def __init__(self):
        self.name = 0
class Building:
   def __init__(self):
       self.number = 100

I want to create a new class from a string (for ex. "School"), which inherits from the above classes. I have done:

School = type("School", (City, Building), {"school_name": "abc"}
s = School()

hasattr(s, "school_name") gives True. However, hasattr(s, "name") gives False.

How can I make sure that:

  1. My dynamically generated class School inherits the attributes of its base class(es)?
  2. How can I add additional attributes to the class School after already creating the class School = type("School", (Building, Home), {"school_name": "abc"}? (Suppose I want to add two new attributes (address, pincode) to the class later on in my script - can I do that?)
Asmita Poddar
  • 524
  • 1
  • 11
  • 27
  • You provide an example of `City` and `Building` but your new type uses `Building` and `Home`... where's that come from? – Jon Clements Jul 04 '22 at 23:01
  • 1
    Both `City.__init__` and `Building.__init__` should be calling `super().__init__`. Neither actually defines any *attributes*, though. Even `City().name` would raise an exception. – chepner Jul 04 '22 at 23:01
  • 1
    Put another way, this has little to do with calling `type` explicitly versus using `class School(Building, Home): ...` instead. (Your call to `type` defines a *class* attribute named `school_name` when `School` is defined.) – chepner Jul 04 '22 at 23:02
  • 1
    You rarely want to defined classes like this, anyway. – chepner Jul 04 '22 at 23:03
  • You can add attributes by defining them in the `dict` dictionary argument. i.e. each key being a function name and the cooresponding value a function `def`. – martineau Jul 04 '22 at 23:04
  • On a side note... it's also not the best use of OOP and inheritance anyway... a "School" should have zero or one city's or building's - it is *not* a mix of city/building. eg: a School should have city/building as attributes - not inherit from them. – Jon Clements Jul 04 '22 at 23:05
  • I'd recommend reading up on "is-a" and "has-a" principles for OOP – Jon Clements Jul 04 '22 at 23:09
  • This is a toy example (StackOverflow recommends giving working snippets of reproducible code, hence a toy example given). The actual class names are different in my actual usecase. I just want to understand how to do the implementation. – Asmita Poddar Jul 04 '22 at 23:10
  • You did a mistake... your code works perfectly: try with `hasattr(s, "school_name")` – cards Jul 04 '22 at 23:12
  • I want the inherited attributes to be giving `True` as well. – Asmita Poddar Jul 04 '22 at 23:13
  • @AsmitaPoddar okay... maybe it's a toy example... you just don't use inheritance like this is all I'm trying to say – Jon Clements Jul 04 '22 at 23:15
  • you should add in the dictionary also the `__init__` which call `super` – cards Jul 04 '22 at 23:20
  • How should I add `__init__` in the dictionary? – Asmita Poddar Jul 04 '22 at 23:23
  • The problem here isn't really that the class is created dynamically, and you would encounter the same problem if you defined your classes this way using a regular class definition statement. it's that your classes are not designed for cooperative multiple inheritance. – juanpa.arrivillaga Jul 05 '22 at 08:45

2 Answers2

1

When you create a class dynamically you can also pass methods, "constructor" __init__ included.

For a class with single parent:

# methods & attrs
d = {"school_name": 'abc',
     '__init__': lambda self, *args, **kwargs:
        super(type(self), self).__init__(*args, **kwargs)}

# dynamic class
School = type("School", (City,), d)

# instance
s = School()

print(hasattr(s, 'name'))
#True
print(s.school_name)
#abc

For a class with two parents:

d = {"school_name": 'abc',
     '__init__': lambda self, *args, **kwargs:
        (City.__init__(self, *args, **kwargs),
        Building.__init__(self, *args, **kwargs))[0]
    }

School = type("School", (City, Building), d)
s = School()

print(hasattr(s, 'name'))
#True
print(hasattr(s, 'number'))
#True

The __init__ is called explicitly for each parent. For more complex stuffs I recommend to write a classical function instead of lambdas.

For a class with two parents, base class-free version:

d = {"school_name": 'abc',
     '__init__': lambda self, *args, **kwargs:
     [cls.__init__(self, *args, **kwargs) for cls in type(self).__bases__][0]
    }

NOTE if dealing with multiple inheritance where parents classes have different signatures (so the parameters of __init__) it could be a big mess if don't pay attention to *args & **kwargs!

cards
  • 3,936
  • 1
  • 7
  • 25
  • this would really be a lot clearner if you stopped trying to use a lambda expression to define `__init__`, which is just clumsy. `lambda` expressions are best for pure functions that are meant to return a value, `__init__` is usually exclusively for side-effects. – juanpa.arrivillaga Jul 05 '22 at 08:47
  • also, this is just not how you should be doing the cooperative multiple inheritance, `super(type(self), self)` is a red flag – juanpa.arrivillaga Jul 05 '22 at 08:47
  • @juanpa.arrivillaga [1] about the lambda: yes, I also mentioned it in my answer... also because functions can be quite complicated. The `___init__` lambda returns a value, `None`. [2] could you tell more about the "red flag"? I am curiuous... The doc says that _"The two argument form"_ for calls outside methods as in these cases – cards Jul 05 '22 at 09:02
1

City’s init method gets override by Building’s init method try

hasattr(s, ‘number’)

and it should return True.

Define your class as

class City:
    name = 0
 
class Building:
    number = 100

This way attributes can be inherited.

For the second question, not sure about what you are asking for, but try

School.address = ‘foo’
School.pincode = ‘bar’
Ezon Zhao
  • 577
  • 2
  • 14
  • class attributes != instance attributes – cards Jul 05 '22 at 09:06
  • @cards Thanks for pointing out this. I put all instance attributes to class attributes, which may solve OP’s problem, but be aware it could lead to undesired behavior, i.e. instance initiated before adding/changing class attributes may or may not have that new attributes. – Ezon Zhao Jul 06 '22 at 02:34