I am very inexperienced with Python so bear with me. I am trying to set a schedule in a RaspiPico to turn a RaspPi 3B+ on at given times. The RaspPi 3B+ turns itself off at the 57th minute of every hour, the Pico is supposed to turn it on at the 24th minute of every hour. I've worked out the code so that when I manually run it through Thonny, it turns the RaspPi 3B+ on and doesnt give any error messages anymore, but when left on its own it doesn't appear to be following a schedule. To clarify there is also an RTC included in this Raspi Sandwich. Here is my code:
from machine import Pin, Timer, RTC, I2C
import time
import binascii
# I2C PINSs
I2C_PORT = 0
I2C_SDA = 20
I2C_SCL = 21
# Times when the B+ should be booted up
# Format is (hours, minutes)
# For now, keep it sorted; we could do this in code, but why not just do it by hand
SCHEDULE = [(0, 24, 0), (0, 56, 0), (1, 24, 0), (1, 56, 0), (2, 24, 0), (2, 56, 0), (3, 24, 0), (3, 56, 0), (4, 24, 0), (4, 56, 0), (5, 24, 0), (5, 56, 0), (6, 24, 0), (6, 56, 0), (7, 24, 0), (7, 56, 0), (8, 24, 0), (8, 56, 0), (9, 24, 0), (9, 56, 0)
, (10, 24, 0), (10, 56, 0), (11, 24, 0), (11, 56, 0), (12, 24, 0), (12, 56, 0), (13, 24, 0), (13, 56, 0), (14, 24, 0), (14, 56, 0), (15, 24, 0), (15, 56, 0), (16, 24, 0), (16, 56, 0), (17, 24, 0), (17, 56, 0), (18, 24, 0), (18, 56, 0), (19, 24, 0), (19, 56, 0),
(20, 24, 0), (20, 56, 0), (21, 24, 0), (21, 56, 0), (22, 24, 0), (22, 56, 0), (23, 24, 0), (23, 56, 0)]
# This function returns the next scheduled boot time
# after the hour, minute we provide
def grab_next_time(time_tuple):
current_hour, current_min, current_sec = time_tuple
# Go through all scheudled boot times from first to last
for h, m, s in SCHEDULE:
# If current hour is before hour, we found the next time
if current_hour < h:
return (h,m, s)
# If its the same hour
elif current_hour == h:
# If its before the minute, we found the next time
if current_min < m:
return (h,m,s)
# if its the same minute
elif current_min == m:
# If its before the second, we found the next time
if current_sec < s:
return (h,m,s)
# If the current hour is not the same or before, lets try the next
# We tried all times in the schedule,
# we're after, so lets start from the begining
return SCHEDULE[0]
# This function returns the time we need to wait in seconds
def wait_time(current_time, next_time):
next_hour, next_min, next_sec = next_time[0], next_time[1], next_time[2]
current_hour, current_min, current_sec = current_time[0], current_time[1], current_time[2]
wait_time = (next_sec - current_sec)
if (next_sec - current_sec) < 0:
wait_time += 60
next_min -= 1
wait_time += (next_min - current_min) * 60
if (next_min - current_min) < 0:
wait_time += (60 * 60)
next_hour -= 1
wait_time += (next_hour - current_hour) * 60 * 60
if (next_hour - current_hour) < 0:
wait_time += (60 * 60 * 24)
return wait_time
def start_local_rtc(date=(2017, 8, 23, 2, 12, 48, 0, 0)):
rtc = machine.RTC()
rtc.datetime(date)
return rtc
class ds3231(object):
NowTime = b'\x00\x45\x13\x02\x24\x05\x21'
w = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
address = 0x68
start_reg = 0x00
alarm1_reg = 0x07
control_reg = 0x0e
status_reg = 0x0f
def __init__(self,i2c_port,i2c_scl,i2c_sda):
self.bus = I2C(i2c_port,scl=Pin(i2c_scl),sda=Pin(i2c_sda))
def read_time(self):
t = self.bus.readfrom_mem(int(self.address),int(self.start_reg),7)
seconds = (t[0]&0x0F) + 10 * ((t[0]>>4)&0x07)
minutes = (t[1]&0x0F) + 10 * ((t[1]>>4)&0x07)
hour = (t[2]&0x0F) + 20 * ((t[2]>>4)&0x02) #hour ( 11 1111)
dow = t[3]&0x07 #week
day = (t[4]&0x0F) + 10 * ((t[4]>>4)&0x03) #day
month = (t[5]&0x1F) + 10 * ((t[5]>>4)&0x01) #month
year = 2000 + ((t[6]&0x0F) + 10 * ((t[6]>>4)&0x0F)) # year
#(year, month, day, weekday, hours, minutes, seconds, subseconds)
return (year, month, day, hour, minutes, seconds, 0, 0)
#print("20%x/%02x/%02x %02x:%02x:%02x %s" %(t[6],t[5],t[4],t[2],t[1],t[0],self.w[t[3]-1]))
# For a second, override the start pin
def start_pi():
boot = Pin(21, Pin.OUT)
boot.value(0)
time.sleep(0.1) # 100 ms should do
boot = Pin(21, Pin.IN) # Setting the pin to IN should make it float
if __name__ == '__main__':
# On startup grab the time from the RTC
hw_rtc = ds3231(I2C_PORT, I2C_SCL, I2C_SDA)
time_rtc = hw_rtc.read_time()
print(time_rtc)
local_rtc = start_local_rtc()
# This is our infinite loop
while True:
current_time = local_rtc.datetime()
current_time = (current_time[4], current_time[5], current_time[6])
print(current_time)
next_time = grab_next_time(current_time)
print("Next time: ", next_time)
wait_time = wait_time(current_time, next_time)
print("Wait time: ", wait_time)
# If within 30 seconds, start the pi
if wait_time < 30:
start_pi()
# If we need to wait for more than 30 seconds, keep waiting
else:
time.sleep(wait_time - 5)
The person I am working with mentioned that the Pico will "poke" the Pi and not stop poking until the specified time. hence why my schedule is listed as "(0,24,0), (0,56,0)". In my head this is supposed to start the "poking" at the 24th minute after midnight and stop at the 56th minute. As was stated before this obviously isn't what is going on. Some help would be appreciated.
Related
**I print the id(Q) in calcul(), but the output is not same in different process, Why? **
Shouldn't the message queue be unique, so why would the output ids be different?
Code
from multiprocessing import Pool, Manager, freeze_support, Lock, Queue
class Model(object):
def __init__(self, id=0):
self.id = id
def act(self, s):
return len(str(s)) - len(str(self.id))
def init(lock1, lock2, lock3):
global model_lock_, text_lock_, rst_lock_
model_lock_ = lock1
text_lock_ = lock2
rst_lock_ = lock3
def calcul(s):
s, Q = s
request = Q.get(True)
rst = request.id
print((s, rst, os.getpid(), id(request), id(Q)))
time.sleep(1)
Q.put(request)
return (s, rst)
if __name__ == "__main__":
ptime = time.time()
freeze_support()
Q = Manager().Queue(5)
for i in range(5):
Q.put(Model(i))
params = [(i, Q) for i in range(100)]
p = Pool(processes=3)
rst = p.map(calcul, params)
Output:
(0, 0, 28836, 2421423514192, 2421423266352)
(9, 1, 26440, 1942850478672, 1942850230832)
(18, 2, 14888, 2433320739408, 2433320479280)
(1, 3, 28836, 2421423517504, 2421423266352)
(10, 0, 26440, 1942850481984, 1942850230832)
(19, 4, 14888, 2433320742720, 2433320479280)
(2, 1, 28836, 2421423514192, 2421423266352)
(20, 2, 14888, 2433320739408, 2433320479280)
(11, 3, 26440, 1942850478672, 1942850230832)
(3, 4, 28836, 2421423517504, 2421423266352)(21, 0, 14888, 2433320742720, 2433320479280)
(12, 1, 26440, 1942850481984, 1942850230832)
(22, 2, 14888, 2433320739408, 2433320479280)(4, 3, 28836, 2421423514192, 2421423266352)
(13, 4, 26440, 1942850478672, 1942850230832)
(5, 0, 28836, 2421423517504, 2421423266352)(14, 1, 26440, 1942850481984, 1942850230832)
(23, 3, 14888, 2433320742720, 2433320479280)
Can someone teach me:
Why the Queue id is not unique, or where I wrote wrong?
How does one obtain SeDebugPrivilege in Python? I believe both the Ctypes api and PyWin32 are both individually capable of doing it, i don't care which API is used. I found this broken code in the wild, it's probably pretty close
import win32api
import win32con
import win32security
#
def get_extra_privs():
# Try to give ourselves some extra privs (only works if we're admin):
# SeBackupPrivilege - so we can read anything
# SeDebugPrivilege - so we can find out about other processes (otherwise OpenProcess will fail for some)
# SeSecurityPrivilege - ??? what does this do?
# Problem: Vista+ support "Protected" processes, e.g. audiodg.exe. We can't see info about these.
# Interesting post on why Protected Process aren't really secure anyway: http://www.alex-ionescu.com/?p=34
th = win32security.OpenProcessToken(win32api.GetCurrentProcess(), win32con.TOKEN_ADJUST_PRIVILEGES | win32con.TOKEN_QUERY)
TokenPrivileges = 1
privs = win32security.GetTokenInformation(th, TokenPrivileges)
newprivs = []
for privtuple in privs:
if privtuple[0] == win32security.LookupPrivilegeValue(remote_server, "SeBackupPrivilege") or privtuple[0] == win32security.LookupPrivilegeValue(remote_server, "SeDebugPrivilege") or privtuple[0] == win32security.LookupPrivilegeValue(remote_server, "SeSecurityPrivilege"):
print ("Added privilege " + str(privtuple[0]))
# privtuple[1] = 2 # tuples are immutable. WHY?!
newprivs.append((privtuple[0], 2)) # SE_PRIVILEGE_ENABLED
else:
newprivs.append((privtuple[0], privtuple[1]))
# Adjust privs
privs = tuple(newprivs)
str(win32security.AdjustTokenPrivileges(th, False , privs))
but it crashes with
File "C:\cygwin64\home\hans\tibia\pybot\pybot.py", line 11, in init
processStuff.get_extra_privs()
File "C:\cygwin64\home\hans\tibia\pybot\processStuff.py", line 20, in get_extra_privs
if privtuple[0] == win32security.LookupPrivilegeValue(remote_server, "SeBackupPrivilege") or privtuple[0] == win32security.LookupPrivilegeValue(remote_server
, "SeDebugPrivilege") or privtuple[0] == win32security.LookupPrivilegeValue(remote_server, "SeSecurityPrivilege"):
TypeError: 'PySID' object is not subscriptable
Problems:
Wrong TokenPrivileges value: it should be 3, as per [MS.Docs]: TOKEN_INFORMATION_CLASS enumeration (winnt.h). Use the value exported by PyWin32
remote_server undefined
LookupPrivilegeValue being called 3 times for each existing privilege (minor optimization)
codee00.py:
#!/usr/bin/env python
import sys
import win32api as wapi
import win32con as wcon
import win32security as wsec
PRIV_NAMES = (
wsec.SE_BACKUP_NAME,
wsec.SE_DEBUG_NAME,
wsec.SE_SECURITY_NAME,
)
def enable_privs(remote_server=None, priv_names=PRIV_NAMES):
priv_ids = sorted(wsec.LookupPrivilegeValue(remote_server, e) for e in priv_names)
print("Privileges to be enabled IDs:", priv_ids)
tok = wsec.OpenProcessToken(wapi.GetCurrentProcess(), wcon.TOKEN_ADJUST_PRIVILEGES | wcon.TOKEN_QUERY)
proc_privs = wsec.GetTokenInformation(tok, wsec.TokenPrivileges)
print("Existing process privileges:", proc_privs)
new_proc_privs = []
need_change = False
for proc_priv in proc_privs:
if proc_priv[0] in priv_ids:
print("Checking privilege " + str(proc_priv[0]))
if proc_priv[1] != wcon.SE_PRIVILEGE_ENABLED:
need_change = True
new_proc_privs.append((proc_priv[0], wcon.SE_PRIVILEGE_ENABLED))
else:
new_proc_privs.append(proc_priv)
print("New process privileges:", new_proc_privs)
if need_change:
modif_privs = wsec.AdjustTokenPrivileges(tok, False, new_proc_privs)
res = wapi.GetLastError()
print("Changed privileges:", modif_privs) # Changed ones
if res != 0:
print("Error (partial) setting privileges:", res)
else:
print("Already set")
#wsec.GetTokenInformation(tok, wsec.TokenPrivileges) # To compare with proc_privs
wapi.CloseHandle(tok)
def main(*argv):
enable_privs()
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
Output:
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q072193556]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
Privileges to be added IDs: [17, 20, 8]
Existing process privileges: ((2, 0), (3, 0), (4, 0), (5, 0), (7, 0), (8, 2), (9, 0), (10, 0), (11, 0), (12, 0), (13, 0), (14, 0), (15, 0), (16, 0), (17, 0), (18, 0), (19, 0), (20, 0), (21, 0), (22, 0), (23, 3), (24, 0), (25, 0), (26, 0), (28, 0), (29, 3), (30, 3), (31, 0), (32, 0), (33, 0), (34, 0), (35, 0), (36, 0))
Checking privilege 8
Checking privilege 17
Checking privilege 20
New process privileges: [(2, 0), (3, 0), (4, 0), (5, 0), (7, 0), (8, 2), (9, 0), (10, 0), (11, 0), (12, 0), (13, 0), (14, 0), (15, 0), (16, 0), (17, 2), (18, 0), (19, 0), (20, 2), (21, 0), (22, 0), (23, 3), (24, 0), (25, 0), (26, 0), (28, 0), (29, 3), (30, 3), (31, 0), (32, 0), (33, 0), (34, 0), (35, 0), (36, 0)]
Changed privileges: ((17, 0), (20, 0))
Done
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q072193556]> :: Attempt running as non Administrator user
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q072193556]> runas /user:test "cmd /k e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe %CD%\code00.py"
Enter the password for test:
Attempting to start cmd /k e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe e:\Work\Dev\StackOverflow\q072193556\code00.py as user "CFATI-5510-0\test" ...
And the newly launched terminal:
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
Privileges to be enabled IDs: [8, 17, 20]
Existing process privileges: ((19, 0), (23, 3), (25, 0), (33, 0), (34, 0))
New process privileges: [(19, 0), (23, 3), (25, 0), (33, 0), (34, 0)]
Already set
Done.
As for the final goal (adding extra new privileges - which is not clearly stated in the question):
It can't be done, which makes perfect sense, as one non privileged process being able to grant itself (administrative) privileges, would beat the purpose of the whole Windows security (privileges and rights) mechanism
Even if one would try adding new privilege entries to the existing list (which automatically implies that current code has design flaws), AdjustTokenPrivileges would (silently) fail with ERROR_NOT_ALL_ASSIGNED (1300). For more details, check [MS.Docs]: Changing Privileges in a Token which states (emphasis is mine):
AdjustTokenPrivileges cannot add or remove privileges from the token. It can only enable existing privileges that are currently disabled or disable existing privileges that are currently enabled.
I am trying to efficiently and evenly distribute events over a year. For each year I have about 10^6 events. Each event should be assigned a date. Below I posted my approach which is quite slow. Do you see a way of speeding this up?
for year in range(start_year, end_year + 1):
for evt in range(events_this_year):
event_date = (datetime.datetime.strptime(str(year) + "-01-01", "%Y-%m-%d") + datetime.timedelta(days=365 * evt / events_this_year)).strftime("%Y-%m-%d")
You transition back and forth from datetime to str, that's the heavy part.
First of all, you can create the year datetime once in the outer loop and not everytime inside, this will already improves performance by ~3.5x (on my machine).
keeping the results as datetime values instead of strings (if that's OK for you) give a 110x(!!) performance boost:
import datetime
from timeit import timeit
start_year = 2010
end_year = 2020
events_this_year = 10 ** 5
def using_strptime():
result = []
for year in range(start_year, end_year + 1):
for evt in range(events_this_year):
event_date = (datetime.datetime.strptime(str(year) + "-01-01", "%Y-%m-%d") + datetime.timedelta(
days=365 * evt / events_this_year)).strftime("%Y-%m-%d")
result.append(event_date)
return result
def using_delta():
result = []
sec_per_event = datetime.timedelta(seconds=365 * 24 * 60 * 60 // events_this_year)
for year in range(start_year, end_year + 1):
year_dt = datetime.datetime(year=year, month=1, day=1)
cur_dt = year_dt
for evt in range(events_this_year):
cur_dt += sec_per_event
result.append(cur_dt.strftime("%Y-%m-%d"))
return result
def using_delta_nostring():
result = []
sec_per_event = datetime.timedelta(seconds=365 * 24 * 60 * 60 // events_this_year)
for year in range(start_year, end_year + 1):
year_dt = datetime.datetime(year=year, month=1, day=1)
cur_dt = year_dt
for evt in range(events_this_year):
cur_dt += sec_per_event
result.append(cur_dt) # no strftime
return result
t1 = timeit('using_strptime()', globals=globals(), number=1)
t2 = timeit('using_delta()', globals=globals(), number=1)
t3 = timeit('using_delta_nostring()', globals=globals(), number=1)
print(t1)
print(t2)
print(t3)
print("Ratios:")
print(t1 / t2)
print(t1 / t3)
Output on my machine:
22.7066284
6.213773400000001
0.20198889999999992
Ratios:
3.654241463005393
112.4152287576199
There is no need to ever create dates by strptime.
Compute the delta between elements and create a list of events based on the enumeration of your events_this_year times the position in this range:
import datetime
start_year = 2021
end_year = 2021
events_this_year = 10**6
# using this to compute the available time handles leap years
day_first = datetime.datetime(start_year,1,1,0,0,0,0)
day_last = datetime.datetime(start_year,12,31,23,59,59,999)
# delta time between events if spaced equally is
# whole available time divided by number of events
ticks_per_event = (day_last - day_first) / events_this_year
# enumerate range of events and use index (i) to multiply the delta
# and add that to the 1st day
events = [(day_first + ticks_per_event * i, f"Event: {e}")
for i,e in enumerate(range(events_this_year))]
print(events[:5], "...", events[-5:], sep="\n")
Output:
[(datetime.datetime(2021, 1, 1, 0, 0), 'Event: 0'),
(datetime.datetime(2021, 1, 1, 0, 0, 31, 535999), 'Event: 1'),
(datetime.datetime(2021, 1, 1, 0, 1, 3, 71998), 'Event: 2'),
(datetime.datetime(2021, 1, 1, 0, 1, 34, 607997), 'Event: 3'),
(datetime.datetime(2021, 1, 1, 0, 2, 6, 143996), 'Event: 4'),
(datetime.datetime(2021, 1, 1, 0, 2, 37, 679995), 'Event: 5')]
...
[(datetime.datetime(2021, 12, 31, 23, 57, 21, 320005), 'Event: 999995'),
(datetime.datetime(2021, 12, 31, 23, 57, 52, 856004), 'Event: 999996'),
(datetime.datetime(2021, 12, 31, 23, 58, 24, 392003), 'Event: 999997'),
(datetime.datetime(2021, 12, 31, 23, 58, 55, 928002), 'Event: 999998'),
(datetime.datetime(2021, 12, 31, 23, 59, 27, 464001), 'Event: 999999')]
Just collecting the dates (you can zip() them back together them with your eventlist) for
from timeit import timeit
k ="""
e = []
for year in range(start_year, end_year + 1):
for evt in range(events_this_year):
e.append( (datetime.datetime.strptime(str(year) + "-01-01", "%Y-%m-%d") + datetime.timedelta(days=365 * evt / events_this_year)).strftime("%Y-%m-%d"))
"""
print(timeit(k, number=2, globals=globals()))
k = '[day_first + ticks_per_event*i for i,e in enumerate(range(events_this_year))]'
print(timeit(k, number=2, globals=globals()))
takes
100.682846539 # yours
1.786883751 # mine
I have a list of objects with attributes qt, cons and consper and have to merge all the objects that have the same consper value. What is the best way to do that? The list is already sorted by consper.
Example:
With a list of objects of the class house:
class house():
def __init__(self, qt, cons, consper):
self.qt = qt
self.cons = cons
self.consper = consper
Turn this list:
l = [
house(2, 20, 10),
house(3, 31, 10),
house(6, 70, 11),
house(2, 40, 20),
house(1, 25, 25)]
Into this list:
l_new = [
house(5, 51, 10),
house(6, 70, 11),
house(2, 40, 20),
house(1, 25, 25)]
By adding the first two objects (because their attribute consper is equivalent)
If the items are already sorted by that attribute, you can use itertools.groupby to get the groups and sum to get the sum for the other attributes. You also have to convert the group to list first, as those are iterators.
>>> from itertools import groupby
>>> house.__repr__ = lambda h: "house(%r, %r, %r)" % (h.qt, h.cons, h.consper)
>>> [house(sum(h.qt for h in g), sum(h.cons for h in g), k)
... for k, g in ((k, list(g)) for k, g in groupby(l, key=lambda h: h.consper))]
[house(5, 51, 10), house(6, 70, 11), house(2, 40, 20), house(1, 25, 25)]
Or using a dictionary:
>>> d = {}
>>> for h in l:
... qt, cons = d.get(h.consper, (0, 0))
... d[h.consper] = qt + h.qt, cons + h.cons
...
>>> [house(a, b, c) for a, (b, c) in d.items()]
[house(25, 1, 25), house(10, 5, 51), house(11, 6, 70), house(20, 2, 40)]
Without using itertools, you could do something like this:
class House():
def __init__(self, qt, cons, consper):
self.qt = qt
self.cons = cons
self.consper = consper
def __str__(self):
return "House(" + str(self.qt) + "," + str(self.cons) + "," + str(self.consper) + ")"
def __repr__(self):
return self.__str__()
def merge_dups(house_list):
res = []
house_map = {}
for h in house_list:
if h.consper in house_map:
other_house = house_map[h.consper]
merged_house = House(h.qt + other_house.qt,
h.cons + other_house.cons,
h.consper)
res.remove(other_house)
res.append(merged_house)
else:
house_map[h.consper] = h
res.append(h)
return res
print(merge_dups([
House(2, 20, 10),
House(3, 31, 10),
House(6, 70, 11),
House(2, 40, 20),
House(1, 25, 25)]))
Output
[House(5,51,10), House(6,70,11), House(2,40,20), House(1,25,25)]
You can use itertools.groupby:
import itertools
class house():
def __init__(self, qt, cons, consper):
self.qt = qt
self.cons = cons
self.consper = consper
def __repr__(self):
return self.__class__.__name__+"({qt}, {cons}, {consper})".format(**self.__dict__)
l = [house(2, 20, 10),
house(3, 31, 10),
house(6, 70, 11),
house(2, 40, 20),
house(1, 25, 25)]
new_l = [(a, [(i.qt, i.cons) for i in list(b)]) for a, b in itertools.groupby(sorted(l, key=lambda x:x.consper), key=lambda x:x.consper)]
final_data = [house(*[sum(i) for i in zip(*b)]+[a]) for a, b in new_l]
Output:
[house(5, 51, 10), house(6, 70, 11), house(2, 40, 20), house(1, 25, 25)]
A simple solution is using a Dictionary as follows:
l = [
house(2, 20, 10),
house(3, 31, 10),
house(6, 70, 11),
house(2, 40, 20),
house(1, 25, 25)]
dic= {}
for x in l :
temp = dic.get(x.consper,house(0,0,0))
x.qt += temp.qt
x.cons += temp.cons
dic[x.consper]=x
print('####################')
for x in dic.keys():
print(x)
for pic,fn,xoff in [(self.PNew,self.New,0),(self.POpen,self.Open,0),(self.PSave,self.Save,0),(self.POrigin,self.SetOrigin,20),(self.PFiducial,self.SetFiducial,0),(self.PLine,self.AddLine,20),(self.PPoint,self.AddPoint,0),(self.PMove,self.AddMove,0),(self.PCircle,self.AddCircle,0),(self.PCall,self.AddCall,0)]: Button(ButtonRow,image=pic,command=fn).pack(side=LEFT,padx=(xoff,0))
items = [
(self.PNew, self.New, 0),
(self.POpen, self.Open, 0),
(self.PSave, self.Save, 0),
(self.POrigin, self.SetOrigin, 20),
(self.PFiducial, self.SetFiducial, 0),
(self.PLine, self.AddLine, 20),
(self.PPoint, self.AddPoint, 0),
(self.PMove, self.AddMove, 0),
(self.PCircle, self.AddCircle, 0),
(self.PCall, self.AddCall, 0)
]
for pic, fn, xoff in items:
Button(ButtonRow, image=pic, command=fn).pack(side=LEFT, padx=(xoff, 0))
You could make it into a function...
def add_button(pic, fn, xoff=0):
Button(ButtonRow, image=pic, command=fn).pack(side=LEFT, padx=(xoff, 0))
add_button(self.PNew, self.New)
add_button(self.POpen, self.Open)
add_button(self.PSave, self.Save)
add_button(self.POrigin, self.SetOrigin, 20)
add_button(self.PFiducial, self.SetFiducial)
add_button(self.PLine, self.AddLine, 20)
add_button(self.PPoint, self.AddPoint)
add_button(self.PMove, self.AddMove)
add_button(self.PCircle, self.AddCircle)
add_button(self.PCall, self.AddCall)