Python: Inner Class - python

I am trying to create a json string from a class and I defined my class as follows:
import json
import ast
from datetime import datetime
import pytz
import time
class OuterClass:
def __init__(self):
self.Header = None
self.Body = None
class Header:
def __init__(self, ID = None, Name = None):
self.ID = ID
self.Name = Name
class Body:
def __init__(self, DateTime=None, Display=None):
self.DateTime = DateTime
self.Display = Display
def current_time_by_timezone(timezone_input):
return datetime.now(pytz.timezone(timezone_input))
if __name__ == '__main__':
response = OuterClass()
header = response.Header('123', 'Some Name')
body = response.Body(current_time_by_timezone('US/Central'), 'NOT VALID')
print(json.dumps(response.__dict__))
I'm getting an error 'TypeError: 'NoneType' object is not callable'. Is it because I'm setting the Header and Body in the OuterClass definition myself to None?

The problem with your code is these lines:
self.Header = None
self.Body = None
These create instance variables named Header and Body on every instance of OuterClass, so you can never access the class variables (the nested classes) via an instance, only via OuterClass itself.
It's not very clear what your intention is with this data structure. Defining a class inside another class doesn't do anything special in Python (by default, you could probably make there be special behavior with special effort, like using a metaclass that makes the inner classes into descriptors). Generally though, there's no implied relationship between the classes.
If you want your OuterClass to create instances of the other two classes, you can do that without nesting their definitions. Just put the class definitions at top level and write a method that creates an instance at an appropriate time and does something useful with it (like binding it to an instance variable).
You might want something like:
def Header:
...
def Response:
def __init__(self):
self.header = None
def make_header(self, *args):
self.header = Header(*args)
return self.header
You could keep the classes nested as long as you don't expect that to mean anything special, just be sure that you don't use the class name as an instance variable, or you'll shadow the name of the nested class (a capitalization difference, like self.header vs self.Header could be enough).

Related

How to get class instance using assigned "id" variable?

Is it possible to get the wooden_sword object using the id variable in the Item class?
class Item:
__ids = count(0)
def __init__(self):
self.id = next(self.__ids)
class Weapon(Item):
def __init__(self, **kwargs):
super().__init__(**kwargs)
wooden_sword = Weapon()
Have the __init__ of Item store to a shared (class attribute) WeakValueDictionary and you can do lookup that way from an alternate constructor (classmethod):
import weakref
class Item:
id_to_item = weakref.WeakValueDictionary()
__ids = count(0)
def __init__(self):
self.id = next(self.__ids)
self.id_to_item[self.id] = self
#classmethod
def from_id(cls, id):
return cls.id_to_item[id]
Item.from_id can raise an exception (probably KeyError like a normal dict; test it) if the object corresponding to that id has been garbage collected; using a plain dict would avoid that issue, though it risks memory "leaks" (not a real leak; the object is available, but might never be used again).

Python: Classes that Depend on Each Other

I'm trying to create a set of classes where each class has a corresponding "array" version of the class. However, I need both classes to be aware of each other. Here is a working example to demonstrate what I'm trying to do. But this requires duplicating a "to_array" in each class. In my actual example, there are other more complicated methods that would need to be duplicated even though the only difference is "BaseArray", "PointArray", or "LineArray". The BaseArray class would similarly have methods that only differ by "BaseObj", "PointObj", or "LineObj".
# ------------------
# Base object types
# ------------------
class BaseObj(object):
def __init__(self, obj):
self.obj = obj
def to_array(self):
return BaseArray([self])
class Point(BaseObj):
def to_array(self):
return PointArray([self])
class Line(BaseObj):
def to_array(self):
return LineArray([self])
# ------------------
# Array object types
# ------------------
class BaseArray(object):
def __init__(self, items):
self.items = [BaseObj(i) for i in items]
class PointArray(BaseArray):
def __init__(self, items):
self.items = [Point(i) for i in items]
class LineArray(BaseArray):
def __init__(self, items):
self.items = [Line(i) for i in items]
# ------------------
# Testing....
# ------------------
p = Point([1])
print(p)
pa = p.to_array()
print(pa)
print(pa.items)
Here is my attempt, which understandably raises an error. I know why I get a NameError and thus I understand why this doesn't work. I'm showing this to make clear what I'd like to do.
# ------------------
# Base object types
# ------------------
class BaseObj(object):
ArrayClass = BaseArray
def __init__(self, obj):
self.obj = obj
def to_array(self):
# By using the "ArrayClass" class attribute here, I can have a single
# "to_array" function on this base class without needing to
# re-implement this function on each subclass
return self.ArrayClass([self])
# In the actual application, there would be other BaseObj methods that
# would use self.ArrayClass to avoid code duplication
class Point(BaseObj):
ArrayClass = PointArray
class Line(BaseObj):
ArrayClass = LineArray
# ------------------
# Array object types
# ------------------
class BaseArray(object):
BaseType = BaseObj
def __init__(self, items):
self.items = [self.BaseType(i) for i in items]
# In the actual application, there would be other BaseArray methods that
# would use self.BaseType to avoid code duplication
class PointArray(BaseArray):
BaseType = Point
class LineArray(BaseArray):
BaseType = Line
# ------------------
# Testing....
# ------------------
p = Point([1])
print(p)
pa = p.to_array()
print(pa)
print(pa.items)
One potential solution would be to just define "ArrayClass" as None for all of the classes, and then after the "array" versions are defined you could monkey patch the original classes like this:
BaseObj.ArrayClass = BaseArray
Point.ArrayClass = PointArray
Line.ArrayClass = LineArray
This works, but it feels a bit unnatural and I suspect there is a better way to achieve this. In case it matters, my use case will ultimate be a plugin to a program that (sadly) still uses Python 2.7, so I need a solution that uses Python 2.7. Ideally the same solution can work in 2.7 and 3+ though.
Here is a solution using decorators. I prefer this to the class attribute assignment ("monkey patch" as I called it) since it keeps things a little more self consistent and clear. I'm happy enough with this, but still interested in other ideas...
# ------------------
# Base object types
# ------------------
class BaseObj(object):
ArrayClass = None
def __init__(self, obj):
self.obj = obj
def to_array(self):
# By using the "ArrayClass" class attribute here, I can have a single
# "to_array" function on this base class without needing to
# re-implement this function on each subclass
return self.ArrayClass([self])
# In the actual application, there would be other BaseObj methods that
# would use self.ArrayClass to avoid code duplication
#classmethod
def register_array(cls):
def decorator(subclass):
cls.ArrayClass = subclass
subclass.BaseType = cls
return subclass
return decorator
class Point(BaseObj):
pass
class Line(BaseObj):
pass
# ------------------
# Array object types
# ------------------
class BaseArray(object):
BaseType = None
def __init__(self, items):
self.items = [self.BaseType(i) for i in items]
# In the actual application, there would be other BaseArray methods that
# would use self.BaseType to avoid code duplication
#Point.register_array()
class PointArray(BaseArray):
pass
#Line.register_array()
class LineArray(BaseArray):
pass
# ------------------
# Testing....
# ------------------
p = Point([1])
print(p)
pa = p.to_array()
print(pa)
print(pa.items)

Create multiple classes or multiple objects in Python?

I have the following problem and I need advice on how to solve it the best technically in Python. As I am new to programming I would like to have some advice.
So I will have the following object and they should store something. Here is an example:
object 1: cash dividends (they will have the following properties)
exdate (will store a list of dates)
recorddate (will store a list of dates)
paydate (will store a list of dates)
ISIN (will store a list of text)
object 2: stocksplits (they will have the following prpoerties)
stockplitratio (will be some ration)
exdate(list of dates)
...
I have tried to solve it like this:
class cashDividends(object):
def __init__(self, _gross,_net,_ISIN, _paydate, _exdate, _recorddate, _frequency, _type, _announceddate, _currency):
self.gross = _gross
self.net = _net
self.ISIN = _ISIN
self.paydate = _paydate
self.exdate = _exdate
self.recorddate = _recorddate
self.frequency = _frequency
self.type = _type
self.announceddate = _announceddate
self.currency = _currency
So if I have this I would have to create another class named stockplits and then define an __init__ function again.
However is there a way where I can have one class like "Corporate Actions" and then have stock splits and cashdividends in there ?
Sure you can! In python you can pass classes to other classes.
Here a simple example:
class A():
def __init__(self):
self.x = 0
class B():
def __init__(self):
self.x = 1
class Container():
def __init__(self, objects):
self.x = [obj.x for obj in objects]
a = A()
b = B()
c = Container([a,b])
c.x
[0,1]
If I understood correctly what you want is an object that has other objects from a class you created as property?
class CorporateActions(object):
def __init__(self, aCashDividend, aStockSplit):
self.cashDividend = aCashDividend
self.stockSplit = aStockSplit
myCashDividends = CashDividends(...) #corresponding parameters here
myStockSplit = StockSplit(...)
myCorporateActions = CorporateActions(myCashDividends, myStockSplit)
Strictly speaking this answer isn't an answer for the final question. However, it is a way to make your life slightly easier.
Consider creating a sort-of template class (I'm using this term loosely; there's no such thing in Python) that does the __init__ work for you. Like this:
class KwargAttrs():
def __init__(self, **kwargs):
for k,v in kwargs.items():
setattr(self, k, v)
def _update(self, **kwargs):
args_dict = {k:(kwargs[k] if k in kwargs else self.__dict__[k]) for k in self.__dict__}
self.__dict__.update(args_dict)
This class uses every supplied keyword argument as an object attribute. Use it this way:
class CashDividends(KwargAttrs):
def __init__(self, gross, net, ISIN, paydate, exdate, recorddate, frequency, type, announceddate, currency):
# save the namespace before it gets polluted
super().__init__(**locals())
# work that might pollute local namespace goes here
# OPTIONAL: update the argument values in case they were modified:
super()._update(**locals())
Using a method like this, you don't have to go through the argument list and assign every single object attribute; it happens automatically.
We bookend everything you need to accomplish in the __init__ method with method calls to the parent-class via super(). We do this because locals() returns a dict every variable in the function's current namespace, so you need to 1.) capture that namespace before any other work pollutes it and 2.) update the namespace in case any work changes the argument values.
The call to update is optional, but the values of the supplied arguments will not be updated if something is done to them after the call to super().__init__() (that is, unless you change the values using setattr(self, 'argname, value)`, which is not a bad idea).
You can continue using this class like so:
class StockSplits(KwargAttrs):
def __init__(self, stocksplitratio, gross, net, ISIN, paydate, exdate, recorddate, frequency, type, announceddate, currency):
super().__init__(**locals())
As mentioned in the other answers you can create a container for our other classes, but you can even do that using this same template class:
class CorporateActions(KwargAttrs):
def __init__(self, stock_splits , cash_dividends):
super().__init__(**locals())
ca = CorporateActions(stock_splits = StockSplits(<arguments>), cash_dividends = CashDividends(<arguments>) )

Inheritance in Python, init method overrriding

I'm trying to understand inheritance in Python. I have 4 different kind of logs that I want to process: cpu, ram, net and disk usage
I decided to implement this with classes, as they're formally the same except for the log file reading and the data type for the data. I have a the following code (log object is a logging object instance of a custom logging class)
class LogFile():
def __init__(self,log_file):
self._log_file=log_file
self.validate_log()
def validate_log(self):
try:
with open(self._log_file) as dummy_log_file:
pass
except IOError as e:
log.log_error(str(e[0])+' '+e[1]+' for log file '+self._log_file)
class Data(LogFile):
def __init__(self,log_file):
LogFile.__init__(self, log_file)
self._data=''
def get_data(self):
return self._data
def set_data(self,data):
self._data=data
def validate_data(self):
if self._data == '':
log.log_debug("Empty data list")
class DataCPU(Data):
def read_log(self):
self.validate_log()
reading and writing to LIST stuff
return LIST
class DataRAM(Data):
def read_log(self):
self.validate_log()
reading and writing to LIST stuff
return LIST
class DataNET(Data):
Now I want my DataNET class to be a object of the Data Class with some more attributes, in particular a dictionary for every one of the interfaces. How can I override the __init__() method to be the same as the Data.__init__() but adding self.dict={} without copying the Data builder? This is, without explicitly specifing the DataNet objects do have a ._data attribute, but inherited from Data.
Just call the Data.__init__() method from DataNET.__init__(), then set self._data = {}:
class DataNET(Data):
def __init__(self, logfile):
Data.__init__(self, logfile)
self._data = {}
Now whatever Data.__init__() does to self happens first, leaving your DataNET initializer to add new attributes or override attributes set by the parent initializer.
In Python 3 classes are already new-style, but if this is Python 2, I'd add object as a base class to LogFile() to make it new-style too:
class LogFile(object):
after which you can use super() to automatically look up the parent __init__ method to call; this has the advantage that in a more complex cooperative inheritance scheme the right methods are invoked in the right order:
class Data(LogFile):
def __init__(self,log_file):
super(Data, self).__init__(log_file)
self._data = ''
class DataNET(Data):
def __init__(self, logfile):
super(DataNET, self).__init__(logfile)
self._data = {}
super() provides you with bound methods, so you don't need to pass in self as an argument to __init__ in that case. In Python 3, you can omit the arguments to super() altogether:
class Data(LogFile):
def __init__(self,log_file):
super().__init__(log_file)
self._data = ''
class DataNET(Data):
def __init__(self, logfile):
super().__init__(logfile)
self._data = {}
Use new style classes (inherit from object) - change definition of LogFile to:
class LogFile(object):
and init method of Data to:
def __init__(self, log_file):
super(Data, self).__init__(log_file)
self._data = ''
Then you can define DataNET as:
class DataNET(Data):
def __init__(self, log_file):
super(DataNET, self).__init__(log_file)
self.dict = {}

Python - Can't change value of item inside a class

I am new to Python, and don't understand why I can't do this.
When I try to change the values in Packet.ItemData from the parent object, it doesn't work. See the "Doesn't work" comments in the code.
import json
from copy import deepcopy
class Event():
__slots__= 'itemName'
def __init__(self, itemName):
self.itemName = itemName
def encode(self):
obj = {}
obj['itemName'] = str(self.itemName)
return json.dumps(obj)
def decode(self, json_Str):
obj = json.loads(json_Str)
self.itemName = obj['itemName']
class Packet():
__slots__= 'pID', 'itemData'
def __init__(self, pID, itemData):
self.pID = pID
self.itemData = itemData
def encode(self):
obj = {}
obj['pID'] = int(self.pID)
obj['itemData'] = str(self.itemData.encode())
return json.dumps(obj)
def decode(self, json_Str):
obj = json.loads(json_Str)
self.pID = obj['pID']
self.itemData = Event(0,'')
defaultEvent = Event('Dflt')
defaultPacket = Packet(1, defaultEvent)
event2 = Event('NoName')
print 'event : ', event2.encode()
packet3 = deepcopy(defaultPacket)
packet3.ItemData = event2; #direct assign doesn't work
packet3.ItemData = deepcopy(event2); #deep copy doesn't work
packet3.ItemData.itemName = 'Hello' #event this doesn't work
print 'packet : ', packet3.encode()
I wanted to wrap up the data so I get exactly what I am expecting when encoding and decoding from JSON.
What FJ pointed out is indeed correct...the name of the attribute is itemData not ItemData.
Being new to Python you may have expected that typo to throw an error...something like 'Packet has no attribute ItemData', but this does not happen. Python classes by default store attributes in a dictionary, so assigning to one that did not exist previously is just like adding a new mapping to the dictionary.
Curiously, your code in a roundabout way highlights an exception to this...and that has to do with the descriptor __slots__. __slots__ changes the class to no longer store attributes in a dynamic dictionary, but in a static structure instead. See Usage of __slots__? for more info.
Normally, when __slots__ is defined you can not assign to an attribute that wasn't specified in __slots__. So you may ask....since you defined __slots__ for your classes why didn't you get an AttributeError? The answer is __slots__ will only work for 'new-style classes' See What is the difference between old style and new style classes in Python? to learn about the distinction between old and new-style classes.
Had you defined Packet to inherit from the base class object like class Packet(object): instead of just class Packet() it would have made it a new-style class and you would have indeed gotten an AttributeError when you tried to assign to ItemData
The attribute name is itemData, not ItemData. If you change all of the packet3.ItemData references to packet3.itemData this should work fine.

Categories