How to make this Python class definition code less ugly - python

What's the most idiomatic way to write a class definition? There's no way that my code below is the best way to do this.
class Course:
crn = course = title = tipe = cr_hours = seats = instructor = days = begin = end = location = exam = ""
def __init__(self, pyQueryRow):
self.crn = Course.get_column(pyQueryRow, 0)
self.course = Course.get_column(pyQueryRow, 1)
self.title = Course.get_column(pyQueryRow, 2)
self.tipe = Course.get_column(pyQueryRow, 3)
self.cr_hours = Course.get_column(pyQueryRow, 4)
self.seats = Course.get_column(pyQueryRow, 5)
self.instructor = Course.get_column(pyQueryRow, 6)
self.days = Course.get_column(pyQueryRow, 7)
self.begin = Course.get_column(pyQueryRow, 8)
self.end = Course.get_column(pyQueryRow, 9)
self.location = Course.get_column(pyQueryRow, 10)
self.exam = Course.get_column(pyQueryRow, 11)
def get_column(row, index):
return row.find('td').eq(index).text()
[First of all python is an awesome language. This is my first project using python and I've made a ridiculous amount of progress already.]

def__init__(self, pyQueryRow):
for i,attr in enumerate("crn course title tipe cr_hours seats instructor"
" days begin end location exam".split()):
setattr(self, attr, self.get_column(pyQueryRow, i))
This way avoids multiple calls to self.get_column
def__init__(self, pyQueryRow):
attrs = ("crn course title tipe cr_hours seats instructor"
" days begin end location exam".split())
values = [td.text for td in pyQueryRow.find('td')]
for attr, value in zip(attrs, values):
setattr(self, attr, value)

Personally, I'd use a dictionary to map the property to column numbers:
class Course:
crn = course = title = tipe = cr_hours = seats = instructor = days = begin = end = location = exam = ""
def __init__(self, pyQueryRow):
course_row_mapping = {
'crn' : 0,
'course' : 1,
'title' : 2,
'tipe' : 3, # You probably mean "type"?
'cr_hours' : 4,
'seats' : 5,
'instructor' : 6,
'days' : 7,
'begin' : 8,
'end' : 9,
'location' : 10,
'exam' : 11,
}
for name, col in course_row_mapping.iteritems():
setattr(self, name, Course.get_column(pyQueryRow, col))
def get_column(row, index):
return row.find('td').eq(index).text()

I'm not sure that there is a "better" way. What you have is certainly quite readable. If you wanted to avoid duplicating the Course.get_column code you could define a lambda for that, as in Matthew Flaschen's answer, eg.
class Course:
def __init__(self, pyQueryRow):
get_column = lambda index: pyQueryRow.find('td').eq(index).text()
self.crn = get_column(0)
self.course = get_column(1)
self.title = get_column(2)
self.tipe = get_column(3)
self.cr_hours = get_column(4)
self.seats = get_column(5)
self.instructor = get_column(6)
self.days = get_column(7)
self.begin = get_column(8)
self.end = get_column(9)
self.location = get_column(10)
self.exam = get_column(11)
Note that you don't need the line that initialises all the fields to "" beforehand - just setting them in __init__ is fine. Edit: in fact, as Matthew says, that sets class fields, not instance fields - I totally missed that.

EDIT: Actually, the best might be:
self.crn, self.course, self.title, self.tipe, self.cr_hours, self.seats,\
self.instructor, self.days, self.begin, self.end, self.location, self.exam = \
[pq(td).text() for td in pyQueryRow.find('td')]
That assumes you've imported PyQuery as pq. This avoids ever using indices at all.
self.crn, self.course, self.title, self.tipe, self.cr_hours, self.seats,\
self.instructor, self.days, self.begin, self.end, self.location, self.exam = \
map(lambda index: get_column(pyQueryRow, index), xrange(0, 12))
or if you want a list comprehension:
self.crn, self.course, self.title, self.tipe, self.cr_hours, self.seats,\
self.instructor, self.days, self.begin, self.end, self.location, self.exam = \
[get_column(pyQueryRow, index) for index in xrange(0, 12)]
I don't know if these are the most idiomatic, but there's definitely less boilerplate.
Also, remove the crn = course =. You're assigning to the class, not the instance.

Personally I feel a class shouldn't really know about the outside world. So move it all out and your class looks much prettier. And also more reusable.
class Course:
def __init__(
self,
crn="",
course="",
title="",
tipe="",
cr_hours="",
seats="",
instructor="",
days="",
begin="",
end="",
location="",
exam=""
):
self.crn = crn
self.course = course
self.title = title
self.tipe = tipe
self.cr_hours = cr_hours
self.seats = seats
self.instructor = instructor
self.days = days
self.begin = begin
self.end = end
self.location = location
self.exam = exam
def course_from_row(row):
column_mapping = {
'crn': 0,
'course': 1,
'title': 2,
'tipe': 3,
'cr_hours': 4,
'seats': 5,
'instructor': 6,
'days': 7,
'begin': 8,
'end': 9,
'location': 10,
'exam': 11
}
course = {}
for attr, col in column_mapping.items():
course[attr] = row.find('td').eq(col).text()
return Course(**course)

Related

Python: *args, grouped by type

I have a class __init__ that accepts variable length arguments. I am trying to figure out how to separate the *args into str, and floats/ints.
So, for example, in my mind that might look like:
class Example():
def __init__(self, *legs, *vals, named_input: float, named_input_2: str):
*legs are a string. *vals are floats & ints.
My goal is that the user can do:
a = Example('1y', '5y', named_input = 100, named_input_2 = 'setting_1')
a.legs = ['1y', '5y']
a.vals = []
a = Example('1y', '5y', 15, named_input = 100, named_input_2 = 'setting_1')
a.legs = ['1y', '5y']
a.vals = [15]
a = Example('1y', 0, 15, 30, named_input = 100, named_input_2 = 'setting_1')
a.legs = ['1y']
a.vals = [ 0, 15, 30,]
At least 1 *leg must always be supplied. vals can be None though.
Overcomplicated it.. just had to do this:
class test():
def __init__(self, *args):
self.legs = []
self.vals = []
for x in args:
if type(x) in [str]:
self.legs.append(x)
elif type(x) in [float, int]:
self.vals.append(x)

Python Class Function not defined

I'm creating a class with function within,
but i keep getting error "name 'direct_report' is not defined"
Basically im tring to make an organization chart, creating a list using the direct_report function to add people under each position
class employee:
def __init__(self, name , title, salary):
self.name = name
self.title = title
self.salary = salary
self.direct_reports_list = direct_report()
def __str__(self):
#otheremp_list = []
print(self.title,'-', self.name)
print('Direct Reports:')
for emp in self.direct_reports_list:
print(emp.title,'-', emp.name)
# otheremp_list.append(emp.direct_reports_list)
#print('other employees:')
#for emp in otheremp_list:
# print(emp.title,'-', emp.name)
# otheremp_list.append(emp.direct_reports_list)
def direct_report(self,value):
print(value)
direct_reports_list = []
direct_reports_list.append(value)
print(direct_reports_list)
return direct_reports_list
ceo = employee("Elon Musk", "CEO",1000000)
devdir = employee("Jeff Bezos","Development Director",500000)
devassoc1 = employee("Beyonce Knowles","Development Associate", 50000)
devassoc2 = employee("Taylor Swift","Development Associate", 50000)
ceo.direct_report(devdir)
ceo.direct_report(devdir2)
devdir.direct_report(devassoc1)
devdir.direct_report(devassoc2)
print(ceo)
The # is my further plan to print the full organization chart, but currently im still stuck at the "direct report" parts
You need to add one indentation level for the classes methods like that:
class employee:
def __init__(self, name , title, salary):
self.name = name
self.title = title
self.salary = salary
self.direct_reports_list = direct_report()
def __str__(self):
#otheremp_list = []
print(self.title,'-', self.name)
print('Direct Reports:')
for emp in self.direct_reports_list:
print(emp.title,'-', emp.name)
# otheremp_list.append(emp.direct_reports_list)
#print('other employees:')
#for emp in otheremp_list:
# print(emp.title,'-', emp.name)
# otheremp_list.append(emp.direct_reports_list)
def direct_report(self,value):
print(value)
direct_reports_list = []
direct_reports_list.append(value)
print(direct_reports_list)
return direct_reports_list
ceo = employee("Elon Musk", "CEO",1000000)
devdir = employee("Jeff Bezos","Development Director",500000)
devassoc1 = employee("Beyonce Knowles","Development Associate", 50000)
devassoc2 = employee("Taylor Swift","Development Associate", 50000)
ceo.direct_report(devdir)
ceo.direct_report(devdir2)
devdir.direct_report(devassoc1)
devdir.direct_report(devassoc2)
print(ceo)
Call the function in this way.
class employee:
def __init__(self, name , title, salary):
self.name = name
self.title = title
self.salary = salary
self.direct_reports_list = self.direct_report()
try self.direct_report() instead as you are calling a method within the class
Try this code.
class employee:
def __init__(self, name , title, salary):
self.name = name
self.title = title
self.salary = salary
self.direct_reports_list = []
def __str__(self):
#otheremp_list = []
print(self.title,'-', self.name)
print('Direct Reports:')
for emp in self.direct_reports_list:
print(emp.title,'-', emp.name)
# otheremp_list.append(emp.direct_reports_list)
#print('other employees:')
#for emp in otheremp_list:
# print(emp.title,'-', emp.name)
# otheremp_list.append(emp.direct_reports_list)
return "Done"
def direct_report(self,value):
self.direct_reports_list.append(value)
ceo = employee("Elon Musk", "CEO",1000000)
devdir = employee("Jeff Bezos","Development Director",500000)
devassoc1 = employee("Beyonce Knowles","Development Associate", 50000)
devassoc2 = employee("Taylor Swift","Development Associate", 50000)
ceo.direct_report(devdir)
devdir.direct_report(devassoc1)
devdir.direct_report(devassoc2)
print(ceo)
print(devdir)
print(devassoc1)
print(devassoc2)

Can someone point me to the reason this constructor is being called twice?

I've noticed that the code prints the date twice to the constructor and am having trouble understanding why since I believe I only instantiate the object once within my code.
This is the constructor
def __init__(self):
self.today = date.today()
print(self.today)
Here is where I instantiate it
self.data = database()
self.schedule_today = self.data.get_bulletin()
Full code for this section of the program with some unfinished functions
class database:
sch_today = ['apt: 8:00', "breakfast", "music", 'apt: 9:00', "therapy", 'apt: 12:00', "lunch"]
test_schedule = []
def __init__(self):
self.today = date.today()
print(self.today)
def get_parse_bulletin_list(self):
temp = []
index = 0
for i in self.sch_today:
if i[0:3] == 'apt':
while index%3 != 0:
temp.append('')
index+=1
temp.append(i)
else:
temp.append(i)
index += 1
return temp
def get_bulletin(self):
n_count = 1
temp = []
ref = self.get_parse_bulletin_list()
for i in ref:
if i[0:3] == 'apt':
temp.append(paper_scrap().get_layout())
n_count = 1
elif not i:
temp.append(Notecard().blank_layout())
#elif i[0:5] == '[hlf]':
#temp.append(Notecard())
elif n_count >= 3: #allign left
temp.append(Notecard())
else:
temp.append(Notecard())
n_count += 1
return temp
def update_schedule(self):
with open('calendar.txt') as r:
pass
class BulletinInterface(RelativeLayout):
def __init__(self, **kwargs):
super(BulletinInterface, self).__init__(**kwargs)
self.data = database()
self.schedule_today = self.data.get_bulletin()
self.l1 = BulletinArea(size_hint=(1,1),
padding=(38, 135, 37, 34),
orientation=('tb-lr'))
self.add_widget(self.l1)
self.b1 = Button(text="test",
background_color=(1, 1, 1, 1),
size_hint=(0.1, 0.1)
)
self.b1.bind(on_press=self.bulletin_init)
self.add_widget(self.b1)
# bulletin board initialize
self.bulletin_init()
def bulletin_init(self, touch=None):
self.init_bulletin(self.schedule_today)
def init_bulletin(self, element_list):
for i in element_list:
self.l1.add_widget(i)
Found the problem after reviewing the construction of the GUI. The KV language and regular python code were both instantiating the GUI, leading to duplicate function calls for everything.

Access name of python objects

I made a class to work with Heating wires:
class Heating_wire:
def __init__(self, ro, L,d,alpha):
self.ro = ro
self.L = L
self.d = d
self.alpha = alpha
self.RT = [1]
self.vector_T = [1]
def get_R20(self):
self.R_20 = self.ro*self.L/(np.pi*(self.d/2)**2)
def calcular_RT(self,vector_temp):
self.vector_T = vector_temp
self.RT = [self.R_20*(1 + temp*self.alpha) for temp in vector_temp ]
return self.RT
instantiate some objects:
kantal = Heating_wire(1.45,0.25,0.3,4e-5)
nicromo = Heating_wire(1.18,0.25,0.3,0.0004)
ferroniquel = Heating_wire(0.86,0.25,0.3,9.3e-4)
wires = [kantal,nicromo,ferroniquel]
And made a plot:
leg = []
vector_temp = np.linspace(20,1000,1000)
for wire in sorted(wires):
wire.get_R20()
wire.get_RT(vector_temp)
line, = plt.plot(wire.vector_T,wire.RT)
leg.append(line)
plt.legend(leg,sorted(wires))
The issue is that I'm not getting the right names in the legend but the reference to the objects:
If I add a name attribute
def __init__(self,name, ro, L,d,alpha):
self.name = name
I can append the names
leg = []
names= []
vector_temp = np.linspace(20,1000,1000)
for wire in sorted(wires):
wire.get_R20()
wire.get_RT(vector_temp)
line, = plt.plot(wire.vector_T,wire.RT)
leg.append(line)
names.append(wire.name)
plt.legend(leg,names,loc='best')
But I wonder if there is a simpler way t solve this using directly the names of the objects in the list of wires:
kantal = Heating_wire(1.45,0.25,0.3,4e-5)
nicromo = Heating_wire(1.18,0.25,0.3,0.0004)
ferroniquel = Heating_wire(0.86,0.25,0.3,9.3e-4)
wires = [kantal,nicromo,ferroniquel]
Just do it like this and there's no duplication:
wires = [
Heating_wire("kantal", 1.45,0.25,0.3,4e-5),
Heating_wire("nicromo", 1.18,0.25,0.3,0.0004),
Heating_wire("ferroniquel", 0.86,0.25,0.3,9.3e-4)
]
To answer your question, no, objects cannot access the names they were given.

How to render DateField with 3 selects

I'm looking for the simplest and cleanest way to render a basic DateField with 3 select.
<select>day</select><select>month</select><select>year</select>
(and if possible use "format" to choose how to display the final render)
You can take advantage of the fact that DateField will handle multiple-valued inputs and join them together with a space, so you can avoid a secondary form and instead just provide a sequence of inputs:
from wtforms.widgets.core import Select, HTMLString, html_params
class SelectDateWidget(object):
FORMAT_CHOICES = {
'%d': [(x, str(x)) for x in range(1, 32)],
'%m': [(x, str(x)) for x in range(1, 13)],
'%y': [(x, str(x)) for x in range(1950, 2014)],
}
def __call__(self, field, **kwargs):
field_id = kwargs.pop('id', field.id)
html = []
for format in field.format.split():
choices = self.FORMAT_CHOICES[format]
id_suffix = format.replace('%', '-')
params = dict(kwargs, name=field.name, id=field_id + id_suffix)
html.append('<select %s>' % html_params(params))
if field.data:
current_value = int(field.data.strftime(format))
else:
current_value = None
for value, label in choices:
selected = (value == current_value)
html.append(Select.render_option(value, label, selected))
html.append('</select>')
return HTMLString(''.join(html))
# Usage
class MyForm(Form):
american_date = DateField(format='%m %d %y', widget=SelectDateWidget())
european_date = DateField(format='%d %m %y', widget=SelectDateWidget())
Final widget: (Support multiple format not only spaces)
class SelectDateWidget(object):
FORMAT_CHOICES = {
'%d': [(x, str(x)) for x in range(1, 32)],
'%m': [(x, str(x)) for x in range(1, 13)]
}
FORMAT_CLASSES = {
'%d': 'select_date_day',
'%m': 'select_date_month',
'%Y': 'select_date_year'
}
def __init__(self, years=range(1930, 2014)):
super(SelectDateWidget, self).__init__()
self.FORMAT_CHOICES['%Y'] = [(x, str(x)) for x in years]
def __call__(self, field, **kwargs):
field_id = kwargs.pop('id', field.id)
html = []
allowed_format = ['%d', '%m', '%Y']
for format in field.format.split():
if (format in allowed_format):
choices = self.FORMAT_CHOICES[format]
id_suffix = format.replace('%', '-')
id_current = field_id + id_suffix
kwargs['class'] = self.FORMAT_CLASSES[format]
try: del kwargs['placeholder']
except: pass
html.append('<select %s>' % html_params(name=field.name, id=id_current, **kwargs))
if field.data:
current_value = int(field.data.strftime(format))
else:
current_value = None
for value, label in choices:
selected = (value == current_value)
html.append(Select.render_option(value, label, selected))
html.append('</select>')
else:
html.append(format)
html.append('<input type="hidden" value="'+format+'" %s></input>' % html_params(name=field.name, id=id_current, **kwargs))
html.append(' ')
return HTMLString(''.join(html))
I used a custom widget and it's working but it's not perfect yet. Other ideas to do this?
class SelectDateWidget(object):
class SelectDateForm(Form):
days = [(x,x) for x in range(1,32)]
months = [(x,x) for x in range(1,13)]
years = [(x,x) for x in range(1950,2014)]
days_select = SelectField(choices=days)
month_select = SelectField(choices=months)
year_select = SelectField(choices=years)
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
date_form = self.SelectDateForm(prefix=field.name)
days_html = date_form.days_select(class_="input-mini").__html__()
month_html = date_form.month_select(class_="input-mini").__html__()
year_html = date_form.year_select(class_="input-small").__html__()
widget_html = field.format
widget_html = widget_html.replace('%d', days_html)
widget_html = widget_html.replace('%m', month_html)
widget_html = widget_html.replace('%Y', year_html)
return HTMLString(widget_html)
class SelectDateField(DateField):
widget = SelectDateWidget()
def __init__(self, label=None, validators=None, **kwargs):
super(SelectDateField, self).__init__(label, validators, **kwargs)
def pre_validate(self, extra):
form = SelectDateWidget.SelectDateForm(prefix=self.name)
try:
date = datetime.datetime.strptime(form.days_select.data+'-'+form.month_select.data+'-'+form.year_select.data, '%d-%m-%Y')
self.data = date
except:
raise StopValidation(gettext(u'Invalid date.'))
class MyForm(Form):
date = SelectDateField(u'Date', validators=[Required(message=_(u'This field is required.'))], format='%d / %m / %Y')

Categories