How can an outer class access data from its inner class? - python-3.x

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)

Related

How __init__ works for inheritance

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/

Multiple classes in Python

I am learning OOPS in Python. I encountered this piece of code during my course.
class Point(object):
def __init__(self,x,y):
self.x=x
self.y=y
class Line(object):
def __init__(self,p1,p2):
self.p1=p1
self.p2=p2
def slope(self):
return (self.p2.y - self.p1.y)/ (self.p2.x-self.p1.x) 
Let's say for Point class I have two instances P1(11,6) and P2(12,3). For class Line, I have one object L1(7,2). What does it mean that self.p2.y? What value would be accessed here?
I have looked at many places but couldn't find this concept?
self refers to the object of that class. the variables after the . operator are attributes
you can use something other than self, but its good practice to use self.
so for P1, self refers to the P1 object.
theres also a version of self for classes (cls) but thats for class methods

Python: why do I need super().__init__() call in metaclasses?

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.

python property referring to property/attribute of member attribute?

I'm wondering if I have:
class A(object):
def __init__(self):
self.attribute = 1
self._member = 2
def _get_member(self):
return self._member
def _set_member(self, member):
self._member = member
member = property(_get_member, _set_member)
class B(object):
def __init__(self):
self._member = A()
def _get_a_member(self):
return self._member.member
def _set_a_member(self, member):
self._member.member = member
member = property(_get_a_member, _set_a_member)
Can I somehow avoid to write get/setters for A.member, and simply refer to the attribute or property of the A object?
Where the get/setters do logic, its of course needed, but if I simply wan't to expose the member/attributes of a member attribute, then writing get/setters seems like overhead.
I think even if I could write the get/setters inline that would help?
I find the question a bit unclear, however I try to explain some context.
Where the get/setters do logic, its of course needed, but if I simply wan't to expose the member/attributes of a member attribute
If there is no logic in getter/setters, then there is no need to define the attribute as a property, but the attribute can be used directly (in any context).
So
class A(object):
def __init__(self):
self.attribute = 1
self.member = 2
class B(object):
def __init__(self):
self.member = A()
B().member.member # returns 2
B().member.member = 10
In some languages, it's considered good practice to abstract instance properties with getter/setter methods, That's not necessarily the case in Python.
Python properties are useful when you'd need more control over the attribute, for example:
when there is logic (validation, etc.)
to define a readonly attribute (so only providing a getter without a setter)
Update (after the comment)
properties are not necessarily a tool to "hide" some internal implementation. Hiding in Python is a bit different than say in Java, due to very dynamic nature of Python language. It's always possible to introspect and even change objects on the fly, you can add new attributes (even methods) to objects on runtime:
b = B()
b.foo = 4 # define a new attribute on runtime
b.foo # returns 4
So Python developers rely more on conventions to hint their intentions of abstractions.
About the polymorphic members, I think it's most natural for Python classes to just share an interface, that's what's meant by Duck typing. So as long as your next implementation of A supports the same interface (provides the same methods for callers), it should not be any issue to change its implementation.
So this is what I came up with - use a method to generate the properties, with the assumption that the obj has an attribute of _member:
def generate_cls_a_property(name):
"""Small helper method for generating a 'dumb' property for the A object"""
def getter(obj):
return getattr(obj._member, name)
def setter(obj, new_value):
setattr(obj._member, name, new_value)
return property(getter, setter)
This allows me to add properties like so:
class B(object):
def __init__(self):
self._member = A()
member = generate_cls_a_property('member') # generates a dumb/pass-through property
I'll accept my own, unless someone tops it within a week.. :)

Implement child class level variable in abstract base class [duplicate]

This question already has answers here:
Counting instances of a class?
(7 answers)
How to count the number of instance of a custom class?
(1 answer)
Closed 4 years ago.
I have made an abstract base class using metaclass=ABCMeta to implement some default methods and properties that all its derived classes should have, something like this:
class BaseClass(metaclass=ABCMeta):
def __init__(self):
self.params = OrderedDict()
# some function all children must implement
#abstractmethod
def fn():
pass
# some property all children will have
#property
def n_params(self):
return len(self.params)
Now, I want to count how many instances of each derived class are created, because I want to print their name with a number. I can easily implement it like this:
class ChildClass(BaseClass):
# nr of instances of ChildClass
_n = 0
def __init__(self, mu, sigma, amp):
# initialize ABC
super().__init__()
ChildClass._n += 1
self.name = f'ChildClass {ChildClass._n}'
# must be overridden by each child class
#staticmethod
def fn(name):
# do stuff
print(f'hello {name}')
However, I need to implement this in each class that derives from BaseClass separately. It would be much more elegant to implement it in BaseClass itself.
I am looking for something that works like the opposite of super(), maybe something like this:
class BaseClass(metaclass=ABCMeta):
# instance counter for derived classes
child()._n = 0
def __init__(self):
self.params = OrderedDict()
child()._n += 1
Can I implement a class level variable ChildClass._n in BaseClass? And if so, how can I access that variable in BaseClass.__init__?
Edit:
To clarify: I already have a base class, from which I derive many other child classes. The fact that all these classes share many properties is one of the main reasons I decided to use a base class in the first place. It thus seems superfluous to need to implement something as simple as an instance counter separately in each derived class.
Accessing a child class variable seems to be already adressed here. The question that remains is, how do I define a class level variable in my abstract base class, that is different for each child class.

Resources