Python class inheritance - python

Happy new years guys!
I'm new to Python and have been experimenting with class inheritance. I created the code below and have a few questions -
Why is shDate3 of type numpy.datetime64 instead of SHDate3? shDate seems to be of type SHDate, which is the behavior I was expecting.
Why can't shDate2 be created? I'm receiving "'an integer is required'" error...
Thanks a lot!
from datetime import *
from numpy import *
class SHDate(date):
def __init__(self, year, month, day):
date.__init__(self, year, month, day)
class SHDate2(date):
def __init__(self, dateString):
timeStruct = strptime(dateString, "%Y-%m-%d")
date.__init__(self, timeStruct.tm_year, timeStruct.tm_mon, timeStruct.tm_mday)
class SHDate3(datetime64):
def __init__(self, dateString):
super(SHDate3, self).__init__(dateString)
if __name__ == '__main__':
shDate = SHDate(2010,1,31)
print type(shDate)
shDate3 = SHDate3("2011-10-11")
print shDate3
print type(shDate3)
shDate2 = SHDate2("2011-10-11")
print shDate2

Quick answers:
Make sure you know when you should use either type or isinstance, they are different. You may want to take a look at this question, it clarifies type and isinstance usage.
You shouldn't be using __init__ to custom your date class, because it is an immutable class. This question provides some discussion on customizing instances for those classes.

Related

OOP: Python - repeated values in method call

I'm new to OOP python and trying to understand how to handle instances, I have a method:
class Object:
things = []
def __init__(self, table):
self.table = table
self.things.append(table)
( ... )
def thingy(self):
return self.db.execute(f"select date, p1, p2 from {self.table}")
def all_things(self):
self.things.extend(
map(lambda t: Object(thing=t + '_thing').thingy(), Constants.THINGS))
return self.things
Now how would I call this object, because my thing is driven by a list from Constants.THINGS, I.E: THINGS = ["table1", "table2" ... ], but in order to create the object to call the method all_things() - I must have a thing set - even tho the method sets the thing on call ...
This feels a little backward, so would appreciate what it is I am misunderstanding as I think I need to change the constructor/object
a = Object(end_date="2020-01-05",
start_date="2020-01-01",
thing=WHAT_TO_PUT_HERE).all_things()
If I add anything to this thing I get a double output
Any help is appreciated
UPDATE:
The desired output would be that thing() will fire, based on a list input provided by Constants.THINGS, if we input: THINGS = ["table1", "table2"] we would expect thingy() to execute twice with:
select date, p1, p2 from table1,
select date, p1, p2 from table2
And this would be added to the things class variable, and then when all_things() finishes we should have the content of the two select statements in a list
However,
Object.things
will actually have [WHAT_TO_PUT_HERE, table_1, table2]
So according to your update, this is what I think you're attempting to do.
class Object:
def __init__(self):
# do some initialization
pass
def thingy(self, table):
return self.db.execute(f"select date, p1, p2 from {table}")
# call the method "thingy" on all Constants.THINGS
def all_things(self):
map(self.thingy, Constants.THINGS)
Then from outside the class you would call it like this.
my_instance = Object()
my_instance.all_things()
I'm assuming the class will also have some setup and teardown of your db connection. As well as some other things but this is simply a minimalistic attempt at giving an example of how it should work.
Okay, so rather than having a class variable which #Axe319 informed me doesn't get reset with every instance as self.table would. I altered the constructor to just have:
class Object:
def __init__(self, table):
self.table = table
self.things = list()
Then when I call the particular method outside the class:
all_things() I can just pass None into the table as the method builds that for me. i.e:
a = Object(thing=None).all_things()
This might be an anti-pattern - again I'm new to OOP, but it's creating something that looks correct.
P.S yes I agree, things, thingy, and the thing was a bad choice for variables for this question...
Thanks

Python 3 Scope between classes in separate files?

I have been researching for ages and cannot find this specific question being asked (so perhaps I am missing something simple!) but I have had trouble separating classes into different .py files.
Scenario:
Main class imports a Settings class file and a Work class file..Settings class populates a list with objects instantiated from an Object class file...
Work class wants to cycle through that list and change values within each of those objects. <-- here is where I come unstuck.
I have tried it by making the values class variables rather than instance. Still I have to import the settings class in the work class in order to write the code to access the value to change. But it wont change the instance of that class within the main class where all these classes are called!
I read an article on Properties. The examples they gave were still examples of different classes within the same file.
Any advice as to what I should be looking at would be greatly appreciated!
This is what I was doing to test it out:
Main File where all will be run from:
import Set_Test
import Test_Code
sting = Set_Test.Settings()
tc = Test_Code.Testy()
ID = sting._settingsID
print(f'Settings ID is: {ID}')
tc.changeVal()
ID = sting._settingsID
print(f'Settings ID is: {ID}')
Set_Test.py:
class Settings:
def __init__(self):
self._settingsID = 1
#property
def settingsID(self):
return self._settingsID
#settingsID.setter
def settingsID(self, value):
self.settingsID = value
Test_Code.py:
import Set_Test
class Testy:
def changeVal(self):
Set_Test.Settings.settingsID = 8
Thanks to stovfl who provided the answer in comments. I managed to decipher what stovfl meant eventually :D
I think!
Well the below code works for anyone who wants to know:
Main:
import Set_Test
import Test_Code
sting = Set_Test.Settings()
tc = Test_Code.Testy()
ID = sting._settingsID
print(f'Settings ID is: {ID}')
tc.changeVal(sting)
ID = sting._settingsID
print(f'Settings ID is: {ID}')
Set_Test.py:
class Settings:
def __init__(self):
self._settingsID = 1
#property
def settingsID(self):
return self._settingsID
#settingsID.setter
def settingsID(self, value):
self._settingsID = value
Test_Code.py
import Set_Test
sting = Set_Test.Settings()
class Testy():
def changeVal(self, sting):
print(sting.settingsID)
sting.settingsID = 8
print(sting.settingsID)

Python statically typed constructor?

first of all, I'm brand new to python, and have basic understanding of c/c++/c# which are all statically typed languages. So can the following be done in python?
I want the variable birthday to be a datetime. So that whenever I instantiate I have to pass a datetime in with the parameters.
import datetime
class Person:
"""class representing a person."""
def __init__(self, name, sirname, gender, birthday):
self.name = name
self.sirname = sirname
self.gender = gender
self.birthday = datetime.date(birthday)
def getage(self):
"""returns age"""
today = datetime.date.today()
return today.year - self.birthday.year
Further down I instantiate as following
BIRTHDAY = datetime.date(1989, 10, 9)
NIELSON = Person('Nielson', 'Jansen', 'Male', BIRTHDAY)
this gives me the error:
TypeError: an integer is required (got type datetime.date)
is my instantiate wrong or should i get the following out of my head asap with python?
self.birthday = datetime.date(birthday)
(Why I would like to do this is so that the getage method always is presented with a datetime.date instead of something random if I make an instantiate mistake.)
PS: also, if my terminology is not correct, don’t hesitate to correct me. :)
I'm assuming that you fix
self.birthday = datetime.date(birthday)
into
self.birthday = birthday
as suggested by jonrsharpe.
Now if you want to check the type of birthday, you can write
assert isinstance(birthday, datetime.date)
at the beginning of the constructor. This is however not a static check because the check will only be performed when the assertion will run.

Python3.5 object and json.dumps() output

I wrote a class that would allow me to add days (integers) to dates (string %Y-%m-%d). The objects of this class need to be JSON serializable.
Adding days in the form of integers to my objects works as expected. However json.dumps(obj) returns too much info ("2016-03-23 15:57:47.926362") for my original object. Why ? How would I need to modify the class to get ""2016-03-23" instead ? Please see the example below.
Code:
from datetime import datetime, timedelta
import json
class Day(str):
def __init__(self, _datetime):
self.day = _datetime
def __str__(self):
return self.day.date().isoformat()
def __repr__(self):
return "%s" % self.day.date().isoformat()
def __add__(self, day):
new_day = self.day + timedelta(days=day)
return Day(new_day).__str__()
def __sub__(self, day):
new_day = self.day - timedelta(days=day)
return Day(new_day).__str__()
if __name__ == "__main__":
today = Day(datetime.today())
print(today) # 2016-03-23
print(json.dumps(today)) # "2016-03-23 15:57:47.926362"
print(today+1) # 2016-03-24
print(json.dumps(today+1)) # "2016-03-24"
print(today-1) # 2016-03-22
print(json.dumps(today-1)) # "2016-03-22"
Update. Here's my final code for those interested:
from datetime import datetime, timedelta
import json
class Day(str):
def __init__(self, datetime_obj):
self.day = datetime_obj
def __new__(self, datetime):
return str.__new__(Day, datetime.date().isoformat())
def __add__(self, day):
new_day = self.day + timedelta(days=day)
return Day(new_day)
def __sub__(self, day):
new_day = self.day - timedelta(days=day)
return Day(new_day)
if __name__ == "__main__":
today = Day(datetime.today())
print(type(today))
print(today) # 2016-03-23
print(json.dumps(today)) # "2016-03-23"
print(today + 1) # 2016-03-24
print(json.dumps(today + 1)) # "2016-03-24"
print(today - 1) # 2016-03-22
print(json.dumps(today - 1)) # "2016-03-22"
print(json.dumps(dict(today=today))) # {"today": "2016-03-23"}
print(json.dumps(dict(next_year=today+365))) # {"next_year": "2017-03-23"}
print(json.dumps(dict(last_year=today-366))) # {"last_year": "2015-03-23"}
Cool! Let's go with it. You are seeing:
print(json.dumps(today)) # "2016-03-23 15:57:47.926362"
Because somewhere in the encoding process, when deciding how to serialize what was passed to it, json.dumps calls isinstance(..., str) on your object. This returns True and your object is serialized like this string it secretly is.
But where does the "2016-03-23 15:57:47.926362" value come from?
When you call day = Day(datetime_obj), two things happen:
__new__ is called to instantiate the object. You haven't provided a __new__ method, so str.__new__ is used.
__init__ is called to initialize the object.
So day = Day(datetime_obj) effectively translates to:
day = str.__new__(Day, datetime_obj)
For json.dumps, your object will be a str, but the value of the str is set to the default string representation of datetime_obj. Which happens to be the full format you are seeing. Builtins, man!
I played around with this, and it seems if you roll your own __new__ (which is slightly exciting territory, tread carefully) which intercepts the str.__new__ call, you ~~should~~ be fine:
class Day(str):
def __new__(self, datetime):
return str.__new__(Day, datetime.date().isoformat())
But you didn't hear it from me if the whole thing catches fire.
PS The proper way would be to subclass JSONEncoder. But there is zero fun in it.
PS2 Oh, shoot, I tested this on 2.7. I may be completely out there, and if I am, just give me a "you tried" badge.
The reason for the json.dumps(today)'s behavior is not as obvious as it might appear at the first glance. To understand the issue, you should be able to answer two questions:
where does the string value that includes the time come from?
why Day.__str__ is not called by json encoder ? Should it?
Here're some prerequisites:
datetime.today() method is similar to datetime.now() -- it includes the current time (hour, minutes, etc). You could use date.today(), to get only date.
str creates immutable objects in Python; its value is set in the __new__ method that you have not overriden and therefore the default conversion str(datetime.today()) is used to initialize Day's value as a string. It creates the string value that includes both date and time in your case. You could override __new__, to get a different string value:
def __new__(cls, _datetime):
return str.__new__(cls, _datetime.date())
Day is a str subclass and therefore its instances are encoded as JSON strings
str methods return str objects instead of the corresponding subclass objects unless you override them e.g.:
>>> class S(str):
... def duplicate(self):
... return S(self * 2)
...
>>> s = S('abc')
>>> s.duplicate().duplicate()
'abcabcabcabc'
>>> s.upper().duplicate()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'duplicate'
s.upper() returns str object instead of S here and the following .duplicate() call fails.
In your case, to create the corresponding JSON string, json.dumps(today) performs an operation (re.sub() call in json.encode.encode_basestring()) on the today object that uses its value as a string i.e., the issue is that neither re.sub() nor encode_basestring() call __str__() method on instances of str subclasses. Even if encode_basestring(s) were as simple as return '"' + s + '"'; the result would be the same: '"' + today returns a str object and Day.__str__ is not called.
I don't know whether re module should call str(obj) in functions that accept isinstance(obj, str). Or whether json.encode.encode_basestring() should do it (or neither).
If you can't fix Day class; you could patch json.encode.encode_basestring() to call str(obj), to get a desirable JSON representation for str subtype instances (if you want to get the value returned by __str__() method -- putting aside whether it is wise to override __str__() on a str subclass in the first place):
import json
for suffix in ['', '_ascii']:
function_name = 'encode_basestring' + suffix
orig_function = getattr(json.encoder, function_name)
setattr(json.encoder, function_name, lambda s,_e=orig_function: _e(str(s)))
Related Python issue: Cannot override JSON encoding of basic type subclasses

Instantiating a unique object every time when using object composition?

As an example, just a couple of dummy objects that will be used together. FWIW this is using Python 2.7.2.
class Student(object):
def __init__(self, tool):
self.tool = tool
def draw(self):
if self.tool.broken != True:
print "I used my tool. Sweet."
else:
print "My tool is broken. Wah."
class Tool(object):
def __init__(self, name):
self.name = name
self.broken = False
def break(self):
print "The %s busted." % self.name
self.broken = True
Hammer = Tool(hammer)
Billy = Student(Hammer)
Tommy = Student(Hammer)
That's probably enough code, you see where I'm going with this. If I call Hammer.break(), I'm calling it on the same instance of the object; if Billy's hammer is broken, so is Tommy's (it's really the same Hammer after all).
Now obviously if the program were limited to just Billy and Tommy as instances of Students, the fix would be obvious - instantiate more Hammers. But clearly I'm asking because it isn't that simple, heh. I would like to know if it's possible to create objects which show up as unique instances of themselves for every time they're called into being.
EDIT: The kind of answers I'm getting lead me to believe that I have a gaping hole in my understanding of instantiation. If I have something like this:
class Foo(object):
pass
class Moo(Foo):
pass
class Guy(object):
def __init__(self, thing):
self.thing = thing
Bill = Guy(Moo())
Steve = Guy(Moo())
Each time I use Moo(), is that a separate instance, or do they both reference the same object? If they're separate, then my whole question can be withdrawn, because it'll ahve to make way for my mind getting blown.
You have to create new instances of the Tool for each Student.
class Student(object):
def __init__(self, tool):
self.tool = tool
def draw(self):
if self.tool.broken != True:
print "I used my tool. Sweet."
else:
print "My tool is broken. Wah."
class Tool(object):
def __init__(self, name):
self.name = name
self.broken = False
def break(self):
print "The %s busted." % self.name
self.broken = True
# Instead of instance, make it a callable that returns a new one
def Hammer():
return Tool('hammer')
# Pass a new object, instead of the type
Billy = Student(Hammer())
Tommy = Student(Hammer())
I'll try to be brief. Well.. I always try to be brief, but my level of success is pretty much random.randint(0, never). So yeah.
Lol. You even failed to be brief about announcing that you will try to be brief.
First, we need to be clear about what "called into being" means. Presumably you want a new hammer every time self.tool = object happens. You don't want a new instance every time, for example, you access the tool attribute, or you'd always a get a new, presumably unbroken, hammer every time you check self.tool.broken.
A couple approaches.
One, give Tool a copy method that produces a new object that should equal the original object, but be a different instance. For example:
class Tool:
def __init__(self, kind):
self.kind = kind
self.broken = False
def copy(self):
result = Tool(self.kind)
result.broken = self.broken
return result
Then in Student's init you say
self.tool = tool.copy()
Option two, use a factory function.
def makehammer():
return Tool(hammer)
class Student:
def __init__(self, factory):
self.tool = factory()
Billy = Student(makehammer)
I can't think any way in Python that you can write the line self.tool = object and have object automagically make a copy, and I don't think you want to. One thing I like about Python is WYSIWYG. If you want magic use C++. I think it makes code hard to understand when you not only can't tell what a line of code is doing, you can't even tell it's doing anything special.
Note you can get even fancier with a factory object. For example:
class RealisticFactory:
def __init__(self, kind, failurerate):
self.kind = kind
self.failurerate = failurerate
def make(self):
result = Tool(self.kind)
if random.random() < self.failurerate:
result.broken = True
if (self.failurerate < 0.01):
self.failurerate += 0.0001
return result
factory = RealisticFactory(hammer, 0.0007)
Billy = Student(factory.make)
Tommy = Student(factory.make) # Tommy's tool is slightly more likely to be broken
You could change your lines like this:
Billy = Student(Tool('hammer'))
Tommy = Student(Tool('hammer'))
That'll produce a distinct instance of your Tool class for each instance of the Student class. the trouble with your posted example code is that you haven't "called the Tool into being" (to use your words) more than once.
Just call Tool('hammer') every time you want to create a new tool.
h1 = Tool('hammer')
h2 = Tool('hammer')
Billy = Student(h1)
Tommy = Student(h2)
Oh wait, I forgot, Python does have magic.
class Student:
def __setattr__(self, attr, value):
if attr == 'tool':
self.__dict__[attr] = value.copy()
else:
self.__dict__[attr] = value
But I still say you should use magic sparingly.
After seeing the tenor of the answers here and remembering the Zen of Python, I'm going to answer my own dang question by saying, "I probably should have just thought harder about it."
I will restate my own question as the answer. Suppose I have this tiny program:
class Item(object):
def __init__(self):
self.broken = False
def smash(self):
print "This object broke."
self.broken = True
class Person(object):
def __init__(self, holding):
self.holding = holding
def using(self):
if self.holding.broken != True:
print "Pass."
else:
print "Fail."
Foo = Person(Item())
Bar = Person(Item())
Foo.holding.smash()
Foo.using()
Bar.using()
The program will return "Fail" for Foo.using() and "Pass" for Bar.using(). Upon actually thinking about what I'm doing, "Foo.holding = Item()" and "Bar.holding = Item()" are clearly different instances. I even ran this dumpy program to prove it worked as I surmised it did, and no surprises to you pros, it does. So I withdraw my question on the basis that I wasn't actually using my brain when I asked it. The funny thing is, with the program I've been working on, I was already doing it this way but assuming it was the wrong way to do it. So thanks for humoring me.

Categories

Resources