I'm having some problems initialising the parent class inside one subclass in Python 2. What I'm trying to do is override parent class attributes with properties in the child class.
Somehow, when I don't use the method _update_rect(self) in the child class setters (e.g. _set_grab(self, ps_value) and _set_grab(self, ps_value)), everything works as expected. But as soon as I use it the initialization of the parent class fails (the print '+++ END GfxObject initialisation' is not reached) and I get AttributeError: 'GfxRect' object has no attribute '_s_grab'.
As I said at the beginning, the code looks correct to me and the problematic method _update_rect only uses real attributes of itself so I have no idea where the error comes. I could avoid using inheritance in the child class as a workaround but I really want to understand what the problem is.
Regards and thanks in advance.
# Extra code to simplify test code
#=================================
class pygame:
class Rect:
def __init__(self, x, y, w, h):
self.x = x
self.y = y
self.w = w
self.h = h
# Classes
#========
class GfxObject(object):
"""
Generic Graphical Object which is the parent class of all the sub-classes below.
"""
def __init__(self):
self.i_x = 0
self.i_y = 0
self.s_grab = 'nw'
class GfxRect(GfxObject):
"""
Class to draw a rectangle.
"""
def __init__(self):
print '--- START GfxObject initialisation'
super(GfxRect, self).__init__()
print '+++ END GfxObject initialisation'
self._i_x = 0
self._s_grab = 'nw'
self._o_rect = None
print self
self._update_rect()
def __str__(self):
return unicode(self).encode('utf8')
def __unicode__(self):
u_out = u'<GfxRect>\n'
u_out += u' .i_x: %s\n' % self.i_x
u_out += u' ._i_x: %s\n' % self._i_x
u_out += u' .s_grab: %s\n' % self.s_grab
u_out += u' ._s_grab: %s\n' % self._s_grab
return u_out
def _get_grab(self):
return self._s_grab
def _get_x(self):
return self._i_x
def _set_grab(self, ps_value):
self._s_grab = ps_value
#self._update_rect()
self._b_redraw = True
def _set_x(self, i_value):
self._i_x = i_value
self._update_rect()
self._b_redraw = True
def _update_rect(self):
"""
Method to update the pygame rectangle object.
:return:
"""
# [1/?] Calculating the deltas for (x,y) based on the grab position
#------------------------------------------------------------------
if self._s_grab == 'nw':
i_dx = 0
elif self._s_grab == 'n':
i_dx = -800 / 2
else:
raise ValueError('Invalid grab value "%s"' % self._s_grab)
# [2/?] Applying the deltas
#--------------------------
i_x = self._i_x + i_dx
self._o_rect = pygame.Rect(i_x, 0, 800, 600)
i_x = property(fget=_get_x, fset=_set_x)
s_grab = property(fget=_get_grab, fset=_set_grab)
# Main code
#==========
if __name__ == '__main__':
o_progbar = GfxRect()
UPDATE: Moving the initialisation of the parent class in the child class after the internal properties seems to fix the problem which it's even more weird for me.
Before (doesn't work)
def __init__(self):
print '--- START GfxObject initialisation'
super(GfxRect, self).__init__()
print '+++ END GfxObject initialisation'
self._i_x = 0
self._s_grab = 'nw'
self._o_rect = None
self._update_rect()
After (works)
def __init__(self):
self._i_x = 0
self._s_grab = 'nw'
self._o_rect = None
print '--- START GfxObject initialisation'
super(GfxRect, self).__init__()
print '+++ END GfxObject initialisation'
self._update_rect()
...but something wrong seems to be happening under the hood. If I add print 'child class "_update_rect" called' to the _update_rect method, I get this output when running the script:
--- START GfxObject initialisation
child class "_update_rect" called <-- ERROR!?
child class "_update_rect" called <-- ERROR!?
+++ END GfxObject initialisation
child class "_update_rect" called <-- this is correct
...
Which means the parent class is calling the child methods when being initialised!?
UPDATE 2: It seems this is the workflow when initialising the child class.
[1] Child.__init__()
[2] Parent.__init__()
[3] self.i_x = 0
[4] Child._set_x(0)
[5] Child._update_rect()
[6] Child._s_grab = 'foo'
The problem appears in step [6] because ._s_grab attribute hasn't been created yet since the initialisation of the Child class is still initialising the Parent class. To me, it's counter-intuitive (and I would say that it's even a bug) the step [3]-[4] when setting the attribute .i_x of the Parent class triggers the property of the Child class.
By moving the initialisation of the parent class at the end of the child class or adding the missing attribute as a Parent class global (not to the instances), the problem disappear.
Your issue is that you're trying to initialise non-existing class properties/variables (you're calling self.i_x, but you've initialised self._i_x, for example) with a method call.
Changing GfxObject to:
class GfxObject(object):
"""
Generic Graphical Object which is the parent class of all the sub-classes below.
"""
def __init__(self):
self._i_x = 0
self._i_y = 0
self._s_grab = 'nw'
Executed script on my machine. Result output:
$ python2 issue.py
--- START GfxObject initialisation
+++ END GfxObject initialisation
<GfxRect>
.i_x: 0
._i_x: 0
.s_grab: nw
._s_grab: nw
EDIT
Funny thing, once I moved i_x and s_grab from GfxObject()'s init, your code worked as a charm. Basically I only changed that class to this:
class GfxObject(object):
"""
Generic Graphical Object which is the parent class of all the sub-classes below.
"""
i_x = None
s_grab = None
def __init__(self):
# self.i_x = None
# self.s_grab = None
#self.s_grab = 'nw'
pass
So, it seems that you're expperiencing same issue I had with Python3 and #property decorator - if I didn't set an object to None or some other value before defining it as a property, it threw no attribute/not defined errors first time I'd try to use that getter/setter.
Related
Assuming the following setup:
class A:
def __init__(self, nodes):
self.nodes=nodes
def update(self, bool_a=True):
if bool_a:
for n in self.nodes:
if hasattr(self.nodes[n], 'update'):
self.nodes[n].update()
class B:
def __init__(self, int_attr=5):
self.int_attr=int_attr
def update(self):
self.int_attr = 0
Let us assume that the list of nodes in class A is in fact a list of instances of class B.
How do I write a unit test for the update method of class A to check whether the update method of each class B node contained in self.nodes of class A was called?
In a more general setup, let us assume that there are multiple classes implementing update method and can be nodes within self.nodes of class A. How do I check that all update methods of self.nodes members were called?
I have tried the following, unsuccessfully:
mock_obj = MagicMock()
#patch('module.A.update', return_value=mock_obj)
def test_update(self, mock_obj):
nodes = {}
nodes['first'] = B(int_attr=1)
nodes['second'] = B(int_attr=2)
test_A = module.A(nodes=nodes)
test_A.update(bool_A=True)
self.assertTrue(mock_obj.called)
as suggested in mocking a function within a class method.
Edit: If we assume this particular case:
import unittest
import mock
from unittest import TestCase
class A:
def __init__(self, nodes):
self.nodes=nodes
def update(self, bool_a=True):
if bool_a:
to_update = [n for n in self.nodes]
while len(to_update) > 0:
if hasattr(self.nodes[to_update[-1]], 'update'):
self.nodes[to_update[-1]].update()
print('Update called.')
if self.nodes[to_update[-1]].is_updated:
to_update.pop()
class B:
def __init__(self, int_attr=5):
self.int_attr=int_attr
self.is_updated = False
def update(self):
self.int_attr = 0
self.is_updated = True
class TestEnsemble(TestCase):
def setUp(self):
self.b1 = B(1)
self.b2 = B(2)
self.b3 = B(3)
self.nodes = {}
self.nodes['1'] = self.b1
self.nodes['2'] = self.b2
self.nodes['3'] = self.b3
self.a = A(self.nodes)
#mock.patch('module.B.update')
def test_update(self, mock_update):
mock_update.return_value = None
self.a.update()
with self.subTest():
self.assertEqual(mock_update.call_count, 3)
Running unittest for this case results in an endless loop, since is_updated attribute never gets set to True because the update method of class B is mocked. How to measure the amount of time B.update was called within A.update in this case?
Update:
Tried this:
#mock.patch('dummy_script.B')
def test_update(self, mock_B):
self.a.update()
with self.subTest():
self.assertEqual(mock_B.update.call_count, 3)
The update function indeed runs 3 times now (I see it in the console output, as "Update called." is printed out three times), but the call_count of update method stays zero. Am I inspecting the wrong attribute / object?
How do I write a unit test for TestA.test_update() to see if B.update() was called?
This is just to give some ideas.
import mock
import unittest
import A
import B
class TestB(unittest.TestCase):
# only mock away update method of class B, this is python2 syntax
#mock.patch.object(B, 'update')
def test_update(self, mockb_update):
# B.update() does not return anything
mockb_update.return_value = None
nodes = {}
nodes['first'] = B(int_attr=1)
nodes['second'] = B(int_attr=2)
test_A = A(nodes)
test_A.update(bool_A=True)
self.assertTrue(mockb_update.called)
How do I check all B.update() were called for all A.nodes?
# same everthing except this
self.assertEqual(mockb_update.call_count, 2)
Running into an endless loop when B.is_udpated is not mocked after OP updated the code
mock a B.is_updated inside __init__ or mock class __init__ is a topic more complicated that the original post
Here is a few thoughts, B.is_updated cannot be just mock.patch, it is only available after the class B is initiated. so the option is to
a) mock B.__init__, or a class constructor
b) mock the whole class B, which is easier in your case, set is_updated to be True, will end the endless loop.
Combining answers from #Gang and #jonrsharpe, the following code snippets solve the questions I have asked above:
How do I write a unit test for TestA.test_update() to see if B.update() was called?
See #Gangs' answer.
How do I check all B.update() were called for all A.nodes?
See #Gangs' answer.
Running into an endless loop when B.is_udpated is not mocked after OP updated the code
As #jonrsharpe suggested, here the solution is to create one mock object per B instance in nodes and check for function calls separately:
class TestA(TestCase):
#mock.patch('module.B')
#mock.patch('module.B')
#mock.patch('module.B')
def test_update(self, mock_B1, mock_B2, mock_B3):
nodes = {}
nodes['1'] = mock_B1
nodes['2'] = mock_B2
nodes['3'] = mock_B3
a = A(nodes)
a.update()
with self.subTest():
self.assertEqual(mock_B1.update.call_count, 1)
with self.subTest():
self.assertEqual(mock_B2.update.call_count, 1)
with self.subTest():
self.assertEqual(mock_B3.update.call_count, 1)
Additionally, if you want for some reason to execute mocked functions (in case they set some flags or variables which affect runtime), one can write a test like this:
def test_fit_skip_ancestors_all(self):
nodes = {}
nodes['1'] = mock_B1
nodes['2'] = mock_B2
nodes['3'] = mock_B3
a = A(nodes)
with mock.patch.object(A.nodes['1'],'update',wraps=A.nodes['1'].update) as mock_B1, \
mock.patch.object(A.nodes['2'], 'update', wraps=A.nodes['2'].update) as mock_B2, \
mock.patch.object(A.nodes['3'], 'update', wraps=A.nodes['3'].update) as mock_B3:
a.update()
with self.subTest():
self.assertEqual(mock_B1.call_count, 1)
with self.subTest():
self.assertEqual(mock_B2.call_count, 1)
with self.subTest():
self.assertEqual(mock_B3.call_count, 1)
class LogicGate(object):
def __init__(self, n):
self.label = n
self.output = None # ????????????
def getOutput(self):
self.output = self.performGateLogic()
return self.output
def getLabel(self):
return self.label
class BinaryGate(LogicGate):
def __init__(self, n): # ?????????????????
LogicGate.__init__(self, n)
self.pinA = None # ??????????????
self.pinB = None # ??????????????
def getPinA(self):
return int(raw_input('Enter Pin A input for gate' + self.getLabel() + '-->'))
def getPinB(self):
return int(raw_input('Enter Pin A input for gate' + self.getLabel() + '-->'))
class UnaryGate(LogicGate):
def __init__(self, n): # ??????????????
LogicGate.__init__(self, n)
self.pin = None # ?????????????
def getPin(self):
return int(raw_input('Enter Pin input for gate' + self.getLabel() + '-->'))
class AndGate(BinaryGate):
def __init__(self, n): # ????????????
BinaryGate.__init__(self, n)
def performGateLogic(self):
a = self.getPinA()
b = self.getPinB()
if a == 1 and b == 1:
return 1
else:
return 0
This code belongs to Problem Solving with Algorithms and Date Structures.
When I remove the lines before the comment '# ????????', the code can run normally.
Why does the author write the code like this?
Whether is it a good code style?
Can I always remove these lines before the comment '# ????????' ?
The author writes the code like that because it is good practice to never have uninitialised members and class parents, static checkers moan if you do.
The reason that it is not good practice is for future maintainability - let us say that the base class, LogicGate, was to gain a new property - say propagation_delay and a new method that allowed simulations to called get_response_time which relied on the current output state and the required, possibly new, state. If all the code that was derived from that class did the correct initialisations then it would all work fine, without any changes. If you remove those lines and such a new method was introduced you would have to go back through all of the child classes adding them back in before your final class would work for that method, with the chance that you would miss one.
Daft as it sounds doing things properly now is actually future laziness - it only takes you seconds when you are creating a class to make sure everything is initialised - debugging an uninitialised class can take hours.
First:
The __init__ functions are the constructors of the classes, you can read about them here.
Second:
Your code will run without those lines but the question is why and is it ok to remove them?
For example if you remove the following init
class UnaryGate(LogicGate): # LogicGate is the superclass
def __init__(self, n):
LogicGate.__init__(self, n)
The constructor of the super-class LogicGate will be called directly.
Third:
Ok, so can we remove the self.xxx = None?
class BinaryGate(LogicGate):
def __init__(self, n):
LogicGate.__init__(self, n)
self.pinA = None
self.pinB = None
We could remove those 2 Lines too but consider this code
bg = BinaryGate("binaryGate1")
print bg.pinA
This would throw an error because pinA is undefined.
If you do not remove the self.pinA = None in __init__ the code will run and None will be printed.
A QPushButton is set 'asCheckable'. Whence toggled, a class bool is changed.
This altered bool allows a method in a different class to proceed, and upon completion of this outside method I need to return the button to its initial state, 'setChecked(False)'.
While I am able to return the class housed bool to its default state at the end of this external method, I am unable to externally access a method which un-clicks the button.
I assume its due to the arguments in the classes init, but these are necessary - and I'm wondering if there is another means to achieve the described workflow.
Related code snips below:
(command in question is distinguished at bottom of 'Class 2')
Class 1:
class shapeCSVeditor(QtGui.QDialog, QtGui.QWidget):
valueShare = []
rowOverride = False# <<=== equivalent to 'override' in 'Class 2'
def __init__(self, iface, fileName, editorType, parent=None):
super(shapeCSVeditor, self).__init__(parent)
self.iface = iface
self.editorType = editorType
self.fileName = filename
self.pushButtonSetBase = QtGui.QPushButton(self)
self.pushButtonSetBase.setText("Set Base Shape")
self.pushButtonSetBase.setCheckable(True)
self.pushButtonSetBase.toggled.connect(self.on_pushButtonSetBase_toggled)
self.layoutHorizontal.addWidget(self.pushButtonSetBase)
#some other things here...
#QtCore.pyqtSlot()
def on_pushButtonSetBase_toggled(self):
shapeCSVeditor.rowOverride = True
pass
def on_BaseRow_Changed(self):
self.pushButtonSetBase.setChecked(False)
return
Class 2:
class CSVModel(QtCore.QAbstractTableModel):
# Establish inital settings and branch processes
def __init__(self, iface, fileName, editorType, parent=None):
super(CSVModel,self).__init__()
self.propertiesFile = r'some file'
self.areaStressFile = r'some other file'
self.iface = iface
self.rows = []
self.editorType = editorType
self.loadCSV()
self.iface.mapCanvas().selectionChanged.connect(self.addRow)
# add rows to the TableView based on object selection(s) in Qgis.mapCanvas
def addRow(self):
override = shapeCSVeditor.rowOverride
selectedFeatures = selectedLayer.selectedFeatures()
if override:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
shapeCSVeditor.rowOverride = False
self.endResetModel()
shapeCSVeditor.on_BaseRow_Changed# <<<=== wrong-diddily!
break
PS - if parentheticals are added to the 'shapeCSVeditor()' 3 arguments are requisite as referenced in the Button class, and if parentheticals are added to 'on_BaseRow_Changed', the return is;
TypeError: unbound method on_BaseRow_Changed() must be called with
shapeCSVeditor instance as first argument (got nothing instead)
What you are doing is strange.
In python, the first argument of a class method is always the object itself.
So, in your:
def on_BaseRow_Changed(self):
self.pushButtonSetBase.setChecked(False)
# return => This return is useless
if you don't provide an object then you can't access the pushbutton.
You didn't gave us all the code but I think you should provide your addRow with the shapeCSVeditor object that you want to update:
def addRow(self, shapeCSVObj):
override = shapeCSVObj.rowOverride
if override:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
shapeCSVObj.rowOverride = False
self.endResetModel()
shapeCSVObj.on_BaseRow_Changed()
break
Somewhere you must have a shapeCSVeditor that is created. You should provide it to you outside class.
Hope this helps.
class shapeCSVeditor(QtGui.QDialog, QtGui.QWidget):
valueShare = []
rowOverride = False
def __init__(self, iface, fileName, editorType, parent=None):
super(shapeCSVeditor, self).__init__(parent)
self.iface = iface
self.editorType = editorType
self.fileName = fileName
self.tableView = QtGui.QTableView(self)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.tableData = CSVModel(self,iface,fileName,editorType)
^^==not implementing 'self' (shapeCSVeditor object) was the problem!
self.tableView.setModel(self.tableData)
...
self.pushButtonSetBase = QtGui.QPushButton(self)
self.pushButtonSetBase.setText("Set Base Shape")
self.pushButtonSetBase.setCheckable(True)
self.pushButtonSetBase.clicked.connect(self.on_pushButtonSetBase_toggled)
...
#QtCore.pyqtSlot()
def on_pushButtonSetBase_toggled(self):
self.rowOverride = True
#QtCore.pyqtSlot()
def on_BaseRow_Changed(self):
self.rowOverride = False
self.pushButtonSetBase.setChecked(False)
///////////////////////////////////////////////////////////////////////////////////////
class CSVModel(QtCore.QAbstractTableModel):
def __init__(self, shapeCSVeditor, iface, fileName, editorType):
super(CSVModel,self).__init__()
self.propertiesFile = r'foo'
self.areaStressFile = r'bar'
self.tableView = shapeCSVeditor <<== proper passing of shapeCSVeditor object! (?)
self.iface = iface
self.rows = []
self.editorType = editorType
self.loadCSV()
self.iface.mapCanvas().selectionChanged.connect(self.addRow)
...
def addRow(self):
selectedFeatures = selectedLayer.selectedFeatures()
if self.tableView.rowOverride:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
self.endResetModel()
self.tableView.rowOverride = False
self.tableView.on_BaseRow_Changed()
Radical. Works for the current needs.
Now the question is if its proper to python 'standards'.
Quite new to writing, so its possible more needs fixed.
High thanks to Plouff for the clues.
I am new to Python language. i am trying to implement stack operation using classes and objects. This is the code i have written till now:
class StackOperation:
def __init__(self):
front = 0
MyStack = [None]*5
def PushValue(self, value):
MyStack[front] = value
front += 1
def PopValue(self):
return MyStack[front -= 1]
def PrintStack(self):
for i in range(len(MyStack)):
print MyStack[i]
stack = StackOperation()
stack.PushValue(10)
print stack.PopValue()
But this code gives me an error when i'm trying to run it. The error says:
"NameError: global name 'MyStack' is not defined"
I'm not sure what mistake i have made.
I would be great full if someone could help me.
Your MyStack and front are not instance variable that's why you can't access it. You have to make it instance variable using self.
class StackOperation:
def __init__(self):
self.front = 0
self.MyStack = [None]*5
def PushValue(self, value):
self.MyStack[self.front] = value
self.front += 1
def PopValue(self):
return self.MyStack[self.front]
def PrintStack(self):
for i in range(len(self.MyStack)):
print self.MyStack[i]
stack = StackOperation()
stack.PushValue(10)
print stack.PopValue()
You'd better use them as instance variable but not global variable since they are all in the same class. Like this:
#!/usr/bin/python
# -*- coding: utf-8 -*-
class StackOperation:
def __init__(self):
self.front = 0
self.MyStack = [None]*5
def PushValue(self, value):
self.MyStack[self.front] = value
self.front += 1
def PopValue(self):
return self.MyStack[self.front]
def PrintStack(self):
for i in range(len(self.MyStack)):
print self.MyStack[i]
stack = StackOperation()
stack.PushValue(10)
print stack.PopValue()
I'm working under python pyramid, with Python3.
I have a model that looks like this:
class OneTimeCode(Base):
__tablename__ = 'otc_one_time_codes'
otc_one_time_code_id = Column(Integer, primary_key=True)
otc_one_time_code = Column(String(32))
otc_usr_user_id = Column(Integer, ForeignKey('usr_users.usr_user_id'), nullable=True)
otc_expire_time = Column(DateTime)
def __init__(self, otc_usr_user_id, otc_expire_time=None):
self.otc_usr_user_id = otc_usr_user_id
if otc_expire_time is None:
self.otc_expire_time = (datetime.now() + timedelta(6*365/12)).isoformat()
else:
self.otc_expire_time = otc_expire_time
#classmethod
def get_code(self, hlength=6):
seed = datetime.now() + timedelta(random.randrange(1,10000))
tmp_hash = hashlib.md5(seed.strftime("%Y-%m-%d %H:%M:%S.%F").encode('utf-8')).hexdigest()
if hlength == 32:
self.otc_one_time_code = tmp_hash
else:
self.otc_one_time_code = tmp_hash[0 : hlength]
print(self.otc_one_time_code)
The problem is, when I instantiate one of these objects and then explicitly call get_code, the print line at the end prints to the screen the code successfully.
However, in my view, if I explicitly try to print that property, it's 'None'
Here's what my view code looks like:
otc = OneTimeCode(
otc_usr_user_id = user.usr_user_id
)
otc.get_code()
pprint.pprint(vars(otc))
session.add(otc)
And the console output looks like this:
0d097c
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x50877d0>, 'otc_expire_time': '2015-02-13T10:56:14.244447', 'otc_usr_user_id': 1} 2014-08-14 22:56:14,245
INFO [sqlalchemy.engine.base.Engine][Dummy-2] INSERT INTO otc_one_time_codes (otc_one_time_code, otc_usr_user_id, otc_expire_time) VALUES (%(otc_one_time_code)s, %(otc_usr_user_id)s, %(otc_expire_time)s) RETURNING otc_one_time_codes.otc_one_time_code_id 2014-08-14 22:56:14,245
INFO [sqlalchemy.engine.base.Engine][Dummy-2] {'otc_one_time_code': None, 'otc_expire_time': '2015-02-13T10:56:14.244447', 'otc_usr_user_id': 1} 2014-08-14 22:56:14,247
INFO [sqlalchemy.engine.base.Engine][Dummy-2] COMMIT
You can see the value inside the model: 0d097c, and also the pprint object, where it doesn't look like the property exists.
Why can't I get access to this property?
Looks like you should be using a #property instead of a OTC, however it also seems like this may be something you DON'T want to calculate each time!
# for all the docstrings, let multi = Multi(2)
class Multi(object):
def __init__(self, attribute):
"""When instantiated, set self.attribute to attribute"""
self.attribute = attribute
#property
def attribute_times_ten(self):
"""accessed via multi.attribute_times_ten
and will return 20. Use properties to signify
a variable that requires some work done to it
that needs to calculated each time it's called."""
return attribute_times_ten
#classmethod
def times_ten(cls, num):
"""Not the best example, but a #classmethod will
give the class as its first argument, NOT the
instance. This is useful in lots of constructor
settings, e.g. CreateClass.fromstring("attributes")"""
return num * 5
def generate_number(self, multiplier):
"""This is just a normal method. This is what I think
you want, tbh, and you should probably call it in your
__init__ method since you NEED this to run in your OTC
for it to work as intended. Methods (like properties)
are automagically passed the instance as the first
argument, so we can CHANGE self.attribute with that."""
self.attribute = self.attribute * multiplier
Docstrings should be self descriptive, but:
multi = Multi(2)
multi.attribute_times_ten # returns 20
Multi.times_ten(8) # returns 80, note the capital M!
multi.generate_number(3) # self.attribute is now 6
multi.attribute_times_ten # returns 60
A real-world case where you might need all of the above:
class _Tile(object):
def __init__(self, x, y):
"""A naive implementation of Tile that doesn't care
what its side length is and doesn't have any properties
to hide its attributes"""
self.x = x
self.y = y
#classmethod
def tiles_to_pixels(cls, tile):
return cls(tile._x * tile.side_length, tile._y * tile.side_length)
#classmethod
def tiles_to_tiles(cls, tile):
return cls(tile._x, tile._y)
class Tile(object):
def __init__(self, x, y, side_length):
"""A tile object in a map"""
self._x = x # x-coord in tiles
self._y = y # y-coord in tiles
self.side_length = side_length # pixels per tile
#property
def in_pixels(self):
"""self.in_pixels returns an object whose .x and .y
correspond to the x and y position IN PIXELS of the
top-left corner of the tile."""
_tile = _Tile.tiles_to_pixels(self)
return _tile
#property
def in_tiles(self):
"""self.in_tiles returns an object whose .x and .y
correspond to the x and y position IN TILES of the
top-left corner of the tile."""
_tile = _Tile.tiles_to_tiles(self)
return _tile
def change_side_length(self, new_length):
"""Use to change the side length. This can break
your whole map since it's naive, so be careful."""
self.side_length = new_length
my_tile = Tile(0,0,32) # 32 pixel tile starting at (0,0)
my_tile.x # NameError, since it's called my_tile._x
my_tile.in_tiles.x # 0
my_tile.in_pixels.y # 0
other_tile = Tile(4,7,32) # 32 pixel tile starting at (4,7)
other_tile.y # NameError, see above
other_tile.in_tiles.y # 7
other_tile.in_pixels.x # 128