What is the best practice when it comes to overloading attributes you inherit from another class? My IDE and linters going bonkers a bit over the fact the attribute I'm overloading does not exist in the init.
class MeleeCombatSession(Script):
def at_script_creation(self):
self.key = "melee_combat_session" # Warning
self.desc = "Session for melee combat." # Warning
self.interval = 5 # Warning
self.persistent = True # Warning
self.db.characters = {} # No Warning
self.obj.ndb.meelee_combat_session = self # No Warning
I can't really quote the inherited classes because there is about 5 or so classes being inheriting here where the key attribute for example is defined within some methods of other classes. But they all define to this class __init__:
def __init__(self, *args, **kwargs):
typeclass_path = kwargs.pop("typeclass", None)
super().__init__(*args, **kwargs)
self.set_class_from_typeclass(typeclass_path=typeclass_path)
I've tried a number of things, but ideally how I'm supposed to define my MeleeCombatSession class is to set the key, description, interval of this script, and if it's persistent or not, which is overloading the children/parent classes it inherits. I can't for example, pop these into it's own init.
One suggestion was to super() it like:
super().__init__("melee_combat_session")
The code all works fine. There are no errors or issues. This is how you define the class in the engine I'm working in. Just want to avoid linting issues, which everyone is saying to ignore.
Related
I cant have 2 init methods in one class because of function overloading. However, why is it possible that when initializing a subclass, im able to define a new __init__ method, and use the super().__init__ method or the parentclass init method within the subclass __init__ method. i'm just a little confused by the concept of 2 __init__ methods functioning at the same time
class Employee:
emps = 0
def __init__(self,name,age,pay):
self.name = name
self.age = age
self.pay = pay
class Developer(Employee):
def __init__(self,name,age,pay,level):
Employee.__init__(self,name,age,pay)
self.level = level
I cant have 2 init methods in one class because of function overloading.
Partially true. You can't have 2 __init__ methods in the same class because the language lacks function overloading. (Libraries can partially restore a limited form of function overloading; see functools.singledispatchmethod for an example.)
i'm just a little confused by the concept of 2 init methods functioning at the same time
But you aren't trying to overload __init__. You are overriding __init__, providing a different definition for Developer than the definition it inherits from Employer. (In fact, Employer is overriding __init__ as well, using its own definition in place of the one it inherits from object.) Each class has only one definition.
In your definition of Developer.__init__, you are simply making an explicit call to the inherited method to do the initialization common to all instances of Employee, before doing the Developer-specific initialization on the same object.
Using super, you are using a form of dynamic lookup to let the method resolution order for instance of Developer decide what the "next" version of __init__ available to Developer to call. For single inheritance, the benefit is little more than avoiding the need to hard-code a reference to Employee. But for multiple inheritance, super is crucial to ensuring that all inherited methods (both the ones you know about and the ones you may not) get called, and more importantly, are called in the right order.
A full discussion of how to properly use super is beyond the scope of this question, I think, but I'll show your two classes rewritten to make the best use of super, and refer you to Python's super() considered super! for more information.
# Main rules:
# 1. *All* classes use super().__init__, even if you are only inheriting
# from object, because you don't know who will use you as a base class.
# 2. __init__ should use keyword arguments, and be prepared to accept any
# keyword arguments.
# 3. All keyword arguments that don't get assigned to your own parameters
# are passed on to an inherited __init__() to process.
class Employee:
emps = 0
def __init__(self, *, name, age, pay, **kwargs):
super().__init__(**kwargs)
self.name = name
self.age = age
self.pay = pay
class Developer(Employee):
def __init__(self, *, level, **kwargs):
super().__init__(**kwargs)
self.level = level
d1 = Developer(name="Alice", age=30, pay=85000, level=1)
To whet your appetite for the linked article, consider
class A:
def __init__(self, *, x, **kwargs):
super().__init__(**kwargs)
self.x = x
class B:
def __init__(self, *, y, **kwargs):
super().__init__(**kwargs)
self.y = y
class C1(A, B):
pass
class C2(B, A):
pass
c1 = C1(x=1, y=2)
c2 = C2(x=4, y=3)
assert c1.x == 1 and c1.y == 2
assert c2.x == 4 and c2.y == 3
The assertions all pass, and both A.__init__ and B.__init__ are called as intended when c1 and c2 are created.
The super() function is used to give access to methods and properties of a parent or sibling class
check out: https://www.geeksforgeeks.org/python-super/
I'm a structured programming guy. So my attempts with object oriented programming are always "work in progress..."
My intent is to have a class which will adapt itself according to an external input. I saw in another post (which I was unable to find again) that I can change the class of an object, so I made this MWE, which works:
class Base:
def __init__(self, name):
self.name = name
def set_text(self, text):
self.text = text
class Terminator(Base):
terminator = '!'
def __init__(self):
super().__init__('terminator')
def get(self):
return self.text + terminator
class Prefix(Base):
def __init__(self):
super().__init__('prefix')
def get(self):
return str(len(self.text)) + self.text
class_list = {
'terminator': Terminator,
'prefix': Prefix
}
class Selector():
def __init__(self, option):
self.__class__ = class_list[option]
def main():
selection = input("Choose 'terminator' or 'prefix': ")
obj = Selector(selection)
obj.set_text('something')
print(obj.get())
if __name__ == '__main__':
main()
Terminator is a class to produce a text terminated with a special character (!); Prefix produces the same text prefixed with its length.
With Selector, I can use o = Selector('prefix') to get o as a Prefix instance.
The question
My question is if I can add extra arguments to Selector and pass them to the respective class. For example:
o = Selector('prefix', number_of_digits = 2) # '05hello' intead of '5hello'
or
o = Selector('terminator', terminator = '$') # use '$' instead of '!'
For now, I couldn't figure out how to accomplish this task. I tried to use *args and **kwargs, but unsuccessfully.
Additional information
The code I'm working on is intended to undergraduate students and I want to make it simple for teaching purposes, so Selector should be used to hide other classes and their details from the students (to hide Terminator and Prefix, for example).
I expect to have about 15 distinct classes to hide behind Selector.
Also, I'm ready to hear I'm completely wrong about this approach if there are alternatives.
Try calling the appropriate class's __init__() manually, and set the variables like you otherwise would:
class Terminator(Base):
# make terminator an instance variable instead of a class variable,
# and set it as an overridable default arg for the constructor
def __init__(self, terminator='!'):
super().__init__('terminator')
self.terminator = terminator
def get(self):
return self.text + self.terminator
class Selector():
def __init__(self, option, *args, **kwargs):
self.__class__ = class_list[option]
self.__class__.__init__(self, *args, **kwargs)
...
o = Selector('terminator', terminator='$')
o.set_text("Hello World")
print(o.get())
# Hello World$
I should leave a disclaimer: what you're trying to do is essentially a version of the Factory method pattern, which is usually easier to maintain if you bundle it into a method instead of messing with class types and reflection:
def Selector(option: str, *args, **kwargs) -> Base:
return class_list[option](*args, **kwargs)
# this will do .__new()__ and .__init__() normally,
# and is indistinguishable from normal class creation
Using a method to do this instead of overriding the class metadata also has the advantage of being easy to fit into a type system (see the type hinting in the above snippet), which is difficult to do with .__init__(). This is a common design pattern in Java, for example, which is very strongly and statically typed, requires a factory method to have a signature with the superclass of anything it could possibly return, and makes it impossible for an object to change its own type at runtime.
The disadvantage of your current approach, dynamically changing .__class__, is that the .__new__() and .__init__() methods which were called on the resulting object will not match with each other (it would be using Selector.__new__() but Terminator.__init__(), for example), which may cause weird and hard-to-diagnose problems in the future. It's a fun experiment, but be knowledgeable of the risks before using this in something you'll have to maintain for a long time.
I have got one question: why do I need to call super().--init--() in metaclasses? Because metaclass is factory of classes, I think we don`t need to call initialization for making objects of class Shop. Or with using super().--init-- we initializing the class? (Because my IDE says, that I should call it. But without super().--init-- nothing happens, my class working without mistakes).
Can you explane me, why?
Thanks in advance!
class Descriptor:
_counter = 0
def __init__(self):
self.attr_name = f'Descriptor attr#{Descriptor._counter}'
Descriptor._counter += 1
def __get__(self, instance, owner):
return self if instance is None else instance.__dict__[self.attr_name]
def __set__(self, instance, value):
if value > 0:
instance.__dict__[self.attr_name] = value
else:
msg = 'Value must be > 0!'
raise AttributeError(msg)
class Shop():
weight = Descriptor()
price = Descriptor()
def __init__(self, name, price, weight):
self.name = name
self.price = price
self.weight = weight
def __repr__(self):
return f'{self.name}: price - {self.price} weight - {self.weight}'
def buy(self):
return self.price * self.weight
class Meta(type):
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict) # <- this is that func. call
for key, value in attr_dict.items():
if isinstance(value, Descriptor): # Here I rename attributes name of descriptor`s object.
value.attr_name = key
#classmethod
def __prepare__(metacls, name, bases):
return OrderedDict()
You don't "need" to - and if your code use no other custom metaclasses, not calling the metaclass'__init__.super() will work just the same.
But if one needs to combine your metaclass with another, through inheritance, without the super() call, it won't work "out of the box": the super() call is the way to ensure all methods in the inheritance chain are called.
And if at first it looks like that a metaclass is extremely rare, and combining metaclasses would likely never take place: a few libraries or frameworks have their own metaclasses, including Python's "abc"s (abstract base classes), PyQT, ORM frameworks, and so on. If any metaclass under your control is well behaved with proper super() calls on the __new__, __init__ and __call__ methods, (if you override those), what you need to do to combine both superclasses and have a working metaclass can be done in a single line:
CompatibleMeta = type("CompatibleMeta", (meta, type(OtherClassBase)), {})
This way, for example, if you want to use the mechanisms in your metaclass in a class using the ABCMeta functionalities in Python, you just do it. The __init__ method in your Meta will call the other metaclass __init__. Otherwise it would not run, and some subtle unexpectd thing would not be initialized in your classes, and this could be a very hard to find bug.
On a side note: there is no need to declare __prepare__ in a metaclass if all it does is creating an OrderedDict on a Python newer than 3.6: Since that version, dicitionaries used as the "locals()" while executing class bodies are ordered by default. Also, if another metaclass you are combining with also have a __prepare__, there is no way to make that work automatically by using "super()" - you have to check the code and verify which of the two __prepare__s should be used, or create a new mapping type with features to attend both metaclasses.
I am trying to better understand how to use sub-classes using a very simple test-case based off of this question/answer.
class Outer():
def __init__(self, x):
self.x = super(Inner, self).__init__
# self.x = Inner(**kwargs)
def add_two(self):
""" """
return self.x + 2
class Inner():
def __init__(self, x=2):
self.x = x
res = Outer(x=3).add_two()
# res = Outer({'x' : 3}).add_two()
print(res)
>> NameError: name 'Inner' is not defined
If I run the same code but make Inner() its own separate class (as opposed to a sub-class of Outer(), I receive the following error.
TypeError: super(type, obj): obj must be an instance or subtype of type
What is the cause of this error and how do I fix this?
Nesting classes in Python (or other languages) seldom make sense. In this case, it is not useful for anything at all.
If on the "Outer" class you want to have an associated instance of "Inner", that should be created as an instance attribute, on the __init__ method for Outer - like this:
class Outer():
def __init__(self, x):
self.x = Inner(x)
# self.x = Inner(**kwargs)
def add_two(self):
""" """
return self.x + 2
class Inner():
def __init__(self, x=2):
self.x = x
Now, taking a step by step look on your original code, and trying to understand better why it does not work:
In Python everything declared in the body of a class becomes an attribute of that class - a single copy of it will be (ordinarily) shared by all instances of that class. Declaring a whole class nested is syntactically legal but gains you nothing: the inner class is not "hidden" from the outside world by the language in any sense: not by the language, neither by the conventions usually followed by Python developers.
If you want users (i.e. other programmers, or yourself in code that makes use of this file), to create instances of "Outer" and refrain from creating instances of "Inner", simply prefix its name with an _. That is a convention in Python code, and developers usually will know that they should not trust any class, function, or other name that starts with a single _ to be safe for use in 3rd party code - that is the closest Python gets to "private" or "protected" members.
Now, getting to the line:
...
self.x = super(Inner, self).__init__
It again makes no sense. super or explicitly referencing a superclass are meant to call superclasses - that is, classes from which you inherit. You created no inheritance relationship in your code, rather one of composition. That is why you get that error message - if you are using the explicit version of super, the object you pass have to be a subclass of the class you are calling super on. And it is not the case here. (Also, doing it in this form, it does not call the method - just references it - all function or method calls are comitted by the use of ())
You can also make Outer inherit from Inner - in this case, Outer will "be" an Inner, no need to keep a reference to it in an attribute - self will mean both an Outer and an Inner class.
In this case, we need a reference to "Inner" when parsing the declaration of "Outer", so it needs to be defined first:
class _Inner():
def __init__(self, x=2):
self.x = x
class Outer(_Inner):
def __init__(self, x):
super().__init__(x)
def add_two(self):
""" """
return self.x + 2
Note the use of parameterless super - one of the major changes for Python 3. If you need to write code still compatible with Python 2, the parameters to super can be explicit, and the call would be super(Outer, self).__init__().
(Again, calling it _Inner will mean that users of your class should not inherit or instantiate from _Inner and should use Outer - and that is a convention in coding style rather than language syntax)
classes can inherit..
class Base:
def __init__(self,name):
self.name = name
class Derived1(Base):
def __init__(self,name):
super().__init__(name)
class Derived2(Base):
def __init__(self,name):
super().__init__(name)
Can a similar thing done for meta classes also?
I have a requirement where some of my classes will have to be both abstract base classes and also my own meta classes (say singleton types..)
Is it possible to do
class Singleton(type):
'''
implementation goes here..
'''
class AbstractSingleton(Singleton,ABCMeta):
'''
What code should go here??
'''
If its possible how to implement the AbstractSingleton class?
Yes, it is possible.
But first things first:
You should not be using metaclasses for creating singletons in Python.
Singletons are a simple concept, and just a custom __new__ method is enough - no need for a metaclass for that.
This simple 4 line normal class code can be used as a mixin, and will turn any derived classes into "singleton" classes - afer the first instance is created, no further instances are created, and the first instance is always returned:
class SingletonBase:
def __new__(cls, *args, **kw):
if not "instance" in cls.__dict__:
cls.instance = super().__new__(cls, *args, **kw)
return cls.instance
Now, if you'd have a real case for another metaclass and needed to combine that with ABCMeta or other metaclass, all you'd have to do is to create a third metaclass that inherits from both metaclasses - if both of them use super in a well behaved way, it would just work.
class SingletonMeta(type):
def __call__(cls, *args, **kw):
# You know - you _really_ should not be using metaclasses for singletons.
if not "instance" in cls.__dict__:
cls.instance = super().__call__(*args, **kw)
return cls.instance
class SingletonAbstractMeta(SingletonMeta, abc.ABCMeta):
pass
class SingleAbstractBase(metaclass=SingleAbstractMeta):
...
For sheer coincidence, earlier this week I used exactly this use case as an example of what can be achieved with a "meta meta class" in Python. By having a special "meta meta class" to the metaclass one wants to combine to another (I even use ABCMeta on the example), it can create the derived combined metaclass just by using the operator " + ", like in
class SingletonMeta(type, metaclass=MM):
...
class AbstractSingletonBase(metaclass=SingletonMeta + abc.ABCMeta):
# code here.
Check the answer here.