Instantiating a subclass python - python

Just a simple class definition withh subclasses to show inheritance
import datetime
class LibaryItem: #The base class definition
def __init__(self, t, a, i): # initialiser method
self.__Title = t
self.__Author_Artist = a
self.__ItemID = i
self.__OnLoan = False
self.DueDate = datetime.date.today()
def GetTitle(self):
return(self.__Title)
# All other Get methods go here
def Borrowing(self):
self.__OnLoan = True
self.__DueDate = self.__DueDate + datetime.timedelta(weeks = 3)
def Returning(self):
self.OnLoan = False
def PrintDetails(self):
print(self.__Title, '; ', self.__Author_Artist,'; ',end='') # end='' Appends a space instead of a newline
print(self.__ItemID, '; ', self.__OnLoan,'; ', self.__DueDate)
class Book(LibaryItem):# A subclass definition
def __init__(self, t, a, i): # Initialiser method
LibaryItem.__init__(self, t, a, i)
# This statement calls the constructor for the base class
self.__IsRequested = False
self.__RequestBy = 0
def GetIsRequested(self):
return(self.__IsRequested)
class CD(LibaryItem):
def __init__(self, t, a, i): # Initialiser method
LibaryItem.__init__(self, t, a, i)
self.__Genre = ""
def GetGenre(self):
return(self.__Genre)
def SetGenre(self, g):
self.__Genre = g
Instantiating a subclass
ThisBook = Book('Title', 'Author', 'ItemID')
ThisCD = CD('Title', 'Author', 'ItemID')
This is my problem here I don't understand why the ThisBook the object's attribute doesn't change from False its default value to True.
# Using A method
print(ThisBook.GetIsRequested())
ThisBook.IsRequested = True
print(ThisBook.GetIsRequested())
Thank you a reason to why this doesn't work would be helpful

You probably meant to do
ThisBook.__IsRequested = True
which you can't do because of name mangling. You could write another setter.
But before you dive too deeply into writing a lot of getters and setters you should be aware that the pythonic way is to not use them. Or, if additional logic is required, to use the #property decorator.
class LibaryItem:
def __init__(self, title, author, itemid): # initialiser method
self.title = title
self.author = author
self.itemid = itemid
self._onloan = False
self.duedate = datetime.date.today()
#property
def onloan(self):
return self._onloan
#onloan.setter
def onloan(self, value):
if value:
self.duedate += datetime.timedelta(weeks = 3)
self._onloan = value
def __str__(self):
return "%s; %s; %s; %s; %s" % (self.title, self.author, self.itemid, self.onloan, self.duedate)
class Book(LibaryItem):
def __init__(self, title, author, itemid):
LibaryItem.__init__(self, title, author, itemid)
self.requested = False
self.requestby = 0
and then
ThisBook = Book('Title', 'Author', 'ItemID')
print(ThisBook.requested)
ThisBook.requested = True
ThisBook.onloan = True
print(ThisBook.duedate)

You can't access a field with 2 underscores prefix like that (see What is the meaning of a single- and a double-underscore before an object name?).
You need to write a proper setter:
def SetIsRequested(self, val):
self.__IsRequested = val
What you are experiencing is the typical silliness of dynamic languages. A field on class can be set w/o being declared and the interpreter can't help you by pointing out that you've just created a new field called "IsRequested" in your class. Saves you some typing but costs you in ability of your interpreter and IDE to prevent you from messing up.

Related

generic Class in Python

I want to do a generic class in Python with one method
This class does not generate its instances
Some attributes are set in the body of the class
Method of this class uses the set attributes and in the generated classes the output of this method depends on these attributes
Method has only one input
I know that without metaclasses will not do, but I do not know how to apply them :)
something like this:
class GenericClass:
attr_a = ''
attr_b = ''
def count(text):
return len(text)/attr_a + attr_b
class A(GenericClass):
attr_a = 2
attr_b = 1
text = "Hello, I'm under the water"
print(A.count(text))
# 14
Defining count as a class method would make that work:
#classmethod
def count(cls, text):
return len(text) / cls.attr_a + cls.attr_b
class GenericClass:
def _count(text, a, b):
return len(text)/a + b
class A(GenericClass):
attr_a = 2
attr_b = 1
def count(text):
return GenericClass._count(text, A.attr_a, A.attr_b)
text = "Hello, I'm under the water"
print(A.count(text))

Conditional method execution in a class instance

I want to create monad that is able to handle mathematical errors gracefully. I created something like:
from enum import Enum
class Monad(Enum):
Safe = 1
Fail = 2
class DivisionMonad:
def __init__(self, value=None, type=Monad.Safe):
self.value = value
self.type = type
def __truediv__(self, val):
if self.type == Monad.Fail:
return DivisionMonad(type=Monad.Fail)
try:
return DivisionMonad(self.value / val)
except:
return DivisionMonad(type=Monad.Fail)
def __repr__(self):
return f'< {self.value} >'
d = DivisionMonad(1)
d = d / 3
print(d.value)
print(d.type)
d = d / 0
print(d.value)
print(d.type)
But I believe this could be simpler by making the class use different methods based on the state of the Monad. Is there a way to create conditional execution of methods based on an instance attribute other than if/else statements?

Decorator to automate "for arg in self.args" process in class methods

I created a child class that extends a single parameter from the Parent to *args and yields the outputs. However, it looks cleaner with a decorator instead of writing for arg in self.args: yield in multiple methods.
# Parent class
class _greet:
def _hello(self,name):
return "hello " + name
def _hey(self,name):
return "hey " + name
# Child class
class Greet(_greet):
def __init__(self,*names):
self.names = names
def hello(self):
for name in self.names:
yield super()._hello(name)
def hey(self):
for name in self.names:
yield super()._hey(name)
Although, all of my tries generated an error, because the decorator cannot "find" self.args.
Edit:
The idea behind this is to get something like:
class Greet(_greet):
def __init__(self,*names):
self.names = names
#args(names)
def hello(self, var=name):
super()._hello(var)
Firstly, It would be perfect if you could extend your parent-class with desired behaviour.
Secondly, you should think over the meaning of behaviour extending. Functions _hello and hello are fundamentally different. _hello takes one additional arg and returns created output. hello takes no additional args and creates generator. So may be you don't need to create child class? May be you need to create absolutely independent class (or new function)? Also all your functions in _greet doesn't use self arg - may be they should be static (via #staticmethod)?
Thirdly, are you sure that you need exactly decorators? There are something like 10 idioms I know to emulate same behaviour. Some of them have better productivity, some of them require a small amount of code. Some works with multiple inheritance - some do not. The realisation you have presented (if I understand it correctly) looks like adapter pattern (with mistakes).
Here it is adapter solution:
from itertools import repeat
class Greet(object):
age = 666
#staticmethod
def hello(name):
return f'hello {name}'
#staticmethod
def hey(name):
return f'hey {name}'
def say_age(self, name):
return f'{name} is {self.age} years old'
def multiple_greet_adapter(adapter):
return lambda self: map(adapter, repeat(self), self.names)
class MultipleGreet0(Greet):
def __init__(self, *names):
self.names = names
#multiple_greet_adapter
def hello_many(self, name):
return super().hello(name)
hey_many = multiple_greet_adapter(lambda self, name: super().hey(name))
say_age_many = multiple_greet_adapter(lambda self, name: super().say_age(name))
One of the disadvantages of such implementation is that you still have to write many similar code. Also this not as productive as we want.
Approach 1 - less code, but not productive as well:
from functools import partial
class MultipleGreet1(Greet):
def __init__(self, *names):
self.names = names
_corresponding_names = dict(
hello_many = 'hello',
hey_many = 'hey',
say_age_many = 'say_age',
)
def __getattr__(self, attr_name):
try:
single_greet_handler = getattr(super(), self._corresponding_names[attr_name])
except KeyError:
raise AttributeError()
else:
return partial(map, single_greet_handler, self.names)
Approach 2 - the same, but with descriptors:
class ManyGreets(object):
def __init__(self, attr_name):
self._attr_name = attr_name
def __get__(self, owner_inst, owner_cls):
if owner_inst is None:
return self
else:
return partial(map, getattr(super(owner_cls, owner_inst), self._attr_name), owner_inst.names)
class MultipleGreet2(Greet):
def __init__(self, *names):
self.names = names
hello_many = ManyGreets('hello')
hey_many = ManyGreets('hey')
say_age_many = ManyGreets('say_age')
Approach 3 - a good way you can do if MultipleGreet is independent class:
def _create_many_greets(single_greet_handler, method=True):
if method:
return lambda self: map(single_greet_handler, repeat(self), self.names)
else:
return lambda self: map(single_greet_handler, self.names)
class MultipleGreet3(object):
def __init__(self, *names):
self.names = names
age = 123
hello_many = _create_many_greets(Greet.hello, False)
hey_many = _create_many_greets(Greet.hey, False)
say_age_many = _create_many_greets(Greet.say_age)
Approach 4 - the way I recommend if MultipleGreet depends on Greet:
class ManyGreetsCreator(object):
def __init__(self, attr_name):
self._attr_name = attr_name
def __set_name__(self, owner_cls, set_name):
attr_name = self._attr_name
many_greets = lambda s: map(getattr(super(owner_cls, s), attr_name), s.names)
setattr(owner_cls, set_name, many_greets)
class MultipleGreet4(Greet):
def __init__(self, *names):
self.names = names
hello_many = ManyGreetsCreator('hello')
hey_many = ManyGreetsCreator('hey')
say_age_many = ManyGreetsCreator('say_age')
Tests:
>>> mg0 = MultipleGreet0('Nick', 'John')
>>> mg1 = MultipleGreet1('Nick', 'John')
>>> mg2 = MultipleGreet2('Nick', 'John')
>>> mg3 = MultipleGreet3('Nick', 'John')
>>> mg4 = MultipleGreet4('Nick', 'John')
>>> list(mg4.hello_many())
['hello Nick', 'hello John']
>>> list(mg0.hello_many()) == list(mg1.hello_many()) == list(mg2.hello_many()) ==\
list(mg3.hello_many()) == list(mg4.hello_many())
True
>>> list(mg0.say_age_many()) == list(mg1.say_age_many()) == list(mg2.say_age_many()) ==\
list(mg4.say_age_many())
True
>>> list(mg4.say_age_many())
['Nick is 666 years old', 'John is 666 years old']
>>> list(mg3.say_age_many())
['Nick is 123 years old', 'John is 123 years old']
You can read more about descriptors, about __getattr__, about super-class. There are also approaches based on __init_subclass__

Python, how to set constants in init of parent class?

I want to set some constants as members of a class and I feel this is the wrong way to do it:
class Unit:
def __init__(self, SYMBOL=None, RATIO_TO_METER=None):
self._symbol = SYMBOL
self._ratio_to_meter = RATIO_TO_METER
def __repr__(self):
return self._symbol
class Inch(Unit):
SYMBOL = 'in'
RATIO_TO_METER = 0.0254
class Metre(Unit):
SYMBOL = 'm'
RATIO_TO_METER = 1
class Yard(Unit):
SYMBOL = 'yd'
RATIO_TO_METER = 0.9144
For instance the __repr__ method returns None. It seems to execute when Unit instantiates and not when I would like to, namely when self._symbol has received a value.
I could make it work by having an __init__ method in each child class but that would not be DRY.
What is the right to do it?
You already set class attributes, there is no need to pass those into __init__. Just use the class attributes directly:
class Unit:
SYMBOL = None
RATIO_TO_METER = None
def __repr__(self):
return self.SYMBOL
class Inch(Unit):
SYMBOL = 'in'
RATIO_TO_METER = 0.0254
class Metre(Unit):
SYMBOL = 'm'
RATIO_TO_METER = 1
class Yard(Unit):
SYMBOL = 'yd'
RATIO_TO_METER = 0.9144
There is little point in setting instance attributes; you already have direct access to the class attributes.

Is there a way to auto generate a __str__() implementation in python?

Being tired manually implementing a string representation for my classes, I was wondering if there is a pythonic way to do that automatically.
I would like to have an output that covers all the attributes of the class and the class name. Here is an example:
class Foo(object):
attribute_1 = None
attribute_2 = None
def __init__(self, value_1, value_2):
self.attribute_1 = value_1
self.attribute_2 = value_2
Resulting in:
bar = Foo("baz", "ping")
print(str(bar)) # desired: Foo(attribute_1=baz, attribute_2=ping)
This question came to mind after using Project Lombok #ToString in some Java projects.
You can iterate instance attributes using vars, dir, ...:
def auto_str(cls):
def __str__(self):
return '%s(%s)' % (
type(self).__name__,
', '.join('%s=%s' % item for item in vars(self).items())
)
cls.__str__ = __str__
return cls
#auto_str
class Foo(object):
def __init__(self, value_1, value_2):
self.attribute_1 = value_1
self.attribute_2 = value_2
Applied:
>>> str(Foo('bar', 'ping'))
'Foo(attribute_2=ping, attribute_1=bar)'
wrote this while falsetru answerred.
Its the same idea, mine is very beginner friendly in terms of reading it, his is much nicer implemented imho
class stringMe(object):
def __str__(self):
attributes = dir(self)
res = self.__class__.__name__ + "("
first = True
for attr in attributes:
if attr.startswith("__") and attr.endswith("__"):
continue
if(first):
first = False
else:
res += ", "
res += attr + " = " + str( getattr(self, attr))
res += ")"
return res
class Foo(stringMe):
attribute_1 = None
attribute_2 = None
def __init__(self, value_1, value_2):
self.attribute_1 = value_1
self.attribute_2 = value_2
bar = Foo("baz", "ping")
print(str(bar)) # desired: Foo(attribute_1=baz, attribute_2=ping)
You can use #dataclass, which automatically generates __init__(), __repr__(), __str__(), and more. You just need to add a #dataclass decorator to your class and add type annotations to the members. You can even remove your __init__() implementation then.
from dataclasses import dataclass
#dataclass
class Foo(object):
attribute_1 : str
attribute_2 : str
bar = Foo("baz", "ping")
print(str(bar)) # Prints: Foo(attribute_1='baz', attribute_2='ping')

Categories

Resources