10
\$\begingroup\$

I posted an answer on StackOverflow about periodic table sometime back and ever since then I've been thinking about improving/optimizing it and worked up a completely different version of it that opens the wiki link once you click on any element. I have created 4 files:

  • load.py that loads the data from the csv file
  • widgets.py that has the classes with the custom widgets on it
  • main.py that takes all these files and put into a working form
  • data.csv with all the data in a csv format

I am not really sure on how I am supposed to paste these files here as I am not frequent in this community. I have uploaded this into a repo too. Anyway, I will include the code for the py files here and a link to the csv file from the repo.

widgets.py:

import tkinter as tk
import webbrowser
from load import read, color_dict

class Element(tk.Frame):
    def __init__(self,master,element,*args,**kwargs):
        tk.Frame.__init__(self,master,*args,**kwargs)
        
        if not element.startswith('*'):
            data   = read(element).values.tolist()
            symbol = data[0]
            at_no  = data[1]
            at_ms  = data[2]
            e_type = data[3]
            wiki   = data[4]
            color  = self.find_color(e_type)
            
            tk.Label(self,text=at_no,font=(0,10),bg=color,takefocus=0).pack(anchor='w')
            tk.Label(self,text=symbol,font=(0,18,'bold'),bg=color,fg='white',takefocus=0).pack()
            tk.Label(self,text=element,font=(0,9),bg=color,takefocus=0).pack()
            tk.Label(self,text=at_ms,font=(0,7),bg=color,takefocus=0).pack()

            self.bind('<Double-1>',lambda e: webbrowser.open(wiki))
            for wid in self.children:
                self.children.get(wid).bind('<Double-1>',lambda e: webbrowser.open(wiki))
            
        else:
            if element == '*':
                text  = 'Lanthanide'
                color = self.find_color(text)   
                
            else:
                text  = 'Actinide'
                color = self.find_color(text)   
            
            tk.Label(self,text=text,bg=color,font=(0,9)).pack(fill="both",expand=1)
            tk.Label(self,text='89-103',font=(0,18,'bold'),bg=color,fg='white').pack(fill="both",expand=1)
            tk.Label(self,text='(Check below)',bg=color,font=(0,8)).pack(fill="both",expand=1)
                
        self['bg'] = color
        self.config(width=100)
        self.config(height=105)
        self.pack_propagate(0)

    def find_color(self,e_type):
        return color_dict[e_type]

class Key(Element):
    def __init__(self,master,e_type,*args,**kwargs):
        tk.Frame.__init__(self,master,*args,**kwargs)

        color = self.find_color(e_type)
        psuedo_img = tk.PhotoImage(height=1,width=1)
        
        tk.Label(self,image=psuedo_img,bg=color,height=15,width=15).pack(side='left')
        tk.Label(self,text=e_type).pack()

class Demo(tk.Frame):
    def __init__(self,master,e_type,*args,**kwargs):
        tk.Frame.__init__(self,master,*args,**kwargs)
        self['width'] = 500
        self['height'] = 150
        
        cnv = tk.Canvas(self)
        cnv.pack()

        wid = Element(self,element=e_type)
        cnv.create_window(250,100,window=wid,tags='win')

        x,y,x1,y1 = 150,60,200,60
        cnv.create_line(x    ,y   ,x1    ,y1   ,arrow='last',tags='arrow')
        cnv.create_line(x+150,y+40,x+200 ,y1+40,arrow='first',tags='arrow')
        cnv.create_line(x    ,y+65,x1    ,y1+65,arrow='last',tags='arrow')
        cnv.create_line(x+150,y+85,x+200 ,y1+85,arrow='first',tags='arrow')

        cnv.create_text(40,47,text='Atomic Number',anchor='nw')
        cnv.create_text(355,90,text='Symbol',anchor='nw')
        cnv.create_text(93,115,text='Element',anchor='nw')
        cnv.create_text(355,132,text='Atomic Mass',anchor='nw')

        self.pack_propagate(0)

if __name__ == '__main__':
    root = tk.Tk()

    Element(root,'Hydrogen').pack(padx=5,pady=20)
    Key(root,e_type='Halogen').pack()
    Demo(root,'Hydrogen').pack()
    
    root.mainloop()

load.py:

import pandas as pd

df  = pd.read_csv('data.csv',index_col=0)
df1 = pd.read_csv('data.csv')

elements = df1['Element'].values.tolist()
types = sorted(list(set(df1['Type'].values.tolist())))

def read(element: str):
    data = df.loc[element]
    return data

main_layout = [['x' ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,'x'],
               ['x' ,'x' ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,'x' ,'x' ,'x' ,'x' ,'x' ,'x'],
               ['x' ,'x' ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,''  ,'x' ,'x' ,'x' ,'x' ,'x' ,'x'],
               ['x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x'],
               ['x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x'],
               ['x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x'],
               ['x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x']]

sec_layout  = [['x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x'],
               ['x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x']]

color_dict = {'Nonmetal':'#feaa91',
    'Noble Gas':'#ffe36b',
    'Halogen':'#dadf23',
    'Alkali Metal':'#fee0b3',
    'Alkaline Earth Metal':'#fdc721',
    'Metalloid':'#e79cf4',
    'Transition Metal':'#77c3f6',
    'Metal':'#ff9fc2',
    'Lanthanide':'#39d8ab',
    'Actinide':'#abdf4b',
    'Unknown Properties':'#b2b3b2'}

main.py:

import tkinter as tk
from tkinter import ttk
from widgets import Element, Key, Demo
from load    import main_layout, sec_layout, elements, types

root = tk.Tk()
root.title('Periodic Table')

# Frame for entire table
table = tk.Frame(root)
table.pack()

# Frame for main elements of table
main_tab = tk.Frame(table)
main_tab.pack()

# Frame for the key of the table
key_tab = ttk.LabelFrame(main_tab,text='Legend')
key_tab.grid(row=0,column=0,columnspan=10,rowspan=3)

# Frame for the demo widget
repr_tab = ttk.Frame(main_tab)
repr_tab.grid(row=0,column=5,columnspan=10,rowspan=3)

# Frame for secondary elements
sec_tab = tk.Frame(table)
sec_tab.pack()

# Heading
tk.Label(main_tab,text='Periodic Table',font=(0,21,'bold')).grid(row=0,column=3,columnspan=10)

# Loop through layout and place elements in the reserved locations
count = 0
for p,i in enumerate(main_layout):
    for q,j in enumerate(i):
        text = elements[count]
        if j:
            # For lanthides in between main table
            if 56 <= count <= 70:
                if count == 56:
                    text = '*'
                else:
                    count = 71
                    text = elements[count]
            
            # For actinoids in between main table
            elif 88 <= count <= 103:
                if count == 88:
                    text = '**'
                else:
                    count = 103
                    text = elements[count]
            
            Element(main_tab,element=text).grid(row=p,column=q,padx=2,pady=2)
            count += 1

# Loop through secondary layout and place elements in the reserved locations
count = 56 # Start position of lantinides
for p,i in enumerate(sec_layout):
    for q,j in enumerate(i):
        text = elements[count]
        # For Actinoids
        if count >= 71:
            text = elements[count+17]
        Element(sec_tab,element=text).grid(row=p,column=q,padx=2,pady=2)
        count += 1

# Placing the legend in 3x4 grid
for i in range(3):
    for j in range(4):
        if 4*i+j < len(types):
            Key(key_tab,e_type=types[4*i+j]).grid(row=i+1,column=j,sticky='w',padx=5,pady=5)

# Placing the representation in the frame
Demo(repr_tab, elements[0]).grid(row=i,column=j+1,rowspan=5)

root.mainloop()

data.csv:

Element,Symbol,AtomicNumber,AtomicMass,Type,WikiLink
Hydrogen,H,1,1.007,Nonmetal,https://en.wikipedia.org/wiki/Hydrogen
Helium,He,2,4.002,Noble Gas,https://en.wikipedia.org/wiki/Helium
Lithium,Li,3,6.941,Alkali Metal,https://en.wikipedia.org/wiki/Lithium
Beryllium,Be,4,9.012,Alkaline Earth Metal,https://en.wikipedia.org/wiki/Beryllium
Boron,B,5,10.811,Metalloid,https://en.wikipedia.org/wiki/Boron
Carbon,C,6,12.011,Nonmetal,https://en.wikipedia.org/wiki/Carbon
Nitrogen,N,7,14.007,Nonmetal,https://en.wikipedia.org/wiki/Nitrogen
Oxygen,O,8,15.999,Nonmetal,https://en.wikipedia.org/wiki/Oxygen
Fluorine,F,9,18.998,Halogen,https://en.wikipedia.org/wiki/Fluorine
Neon,Ne,10,20.18,Noble Gas,https://en.wikipedia.org/wiki/Neon
Sodium,Na,11,22.99,Alkali Metal,https://en.wikipedia.org/wiki/Sodium
Magnesium,Mg,12,24.305,Alkaline Earth Metal,https://en.wikipedia.org/wiki/Magnesium
Aluminum,Al,13,26.982,Metal,https://en.wikipedia.org/wiki/Aluminum
Silicon,Si,14,28.086,Metalloid,https://en.wikipedia.org/wiki/Silicon
Phosphorus,P,15,30.974,Nonmetal,https://en.wikipedia.org/wiki/Phosphorus
Sulfur,S,16,32.065,Nonmetal,https://en.wikipedia.org/wiki/Sulfur
Chlorine,Cl,17,35.453,Halogen,https://en.wikipedia.org/wiki/Chlorine
Argon,Ar,18,39.948,Noble Gas,https://en.wikipedia.org/wiki/Argon
Potassium,K,19,39.098,Alkali Metal,https://en.wikipedia.org/wiki/Potassium
Calcium,Ca,20,40.078,Alkaline Earth Metal,https://en.wikipedia.org/wiki/Calcium
Scandium,Sc,21,44.956,Transition Metal,https://en.wikipedia.org/wiki/Scandium
Titanium,Ti,22,47.867,Transition Metal,https://en.wikipedia.org/wiki/Titanium
Vanadium,V,23,50.942,Transition Metal,https://en.wikipedia.org/wiki/Vanadium
Chromium,Cr,24,51.996,Transition Metal,https://en.wikipedia.org/wiki/Chromium
Manganese,Mn,25,54.938,Transition Metal,https://en.wikipedia.org/wiki/Manganese
Iron,Fe,26,55.845,Transition Metal,https://en.wikipedia.org/wiki/Iron
Cobalt,Co,27,58.933,Transition Metal,https://en.wikipedia.org/wiki/Cobalt
Nickel,Ni,28,58.693,Transition Metal,https://en.wikipedia.org/wiki/Nickel
Copper,Cu,29,63.546,Transition Metal,https://en.wikipedia.org/wiki/Copper
Zinc,Zn,30,65.38,Transition Metal,https://en.wikipedia.org/wiki/Zinc
Gallium,Ga,31,69.723,Metal,https://en.wikipedia.org/wiki/Gallium
Germanium,Ge,32,72.64,Metalloid,https://en.wikipedia.org/wiki/Germanium
Arsenic,As,33,74.922,Metalloid,https://en.wikipedia.org/wiki/Arsenic
Selenium,Se,34,78.96,Nonmetal,https://en.wikipedia.org/wiki/Selenium
Bromine,Br,35,79.904,Halogen,https://en.wikipedia.org/wiki/Bromine
Krypton,Kr,36,83.798,Noble Gas,https://en.wikipedia.org/wiki/Krypton
Rubidium,Rb,37,85.468,Alkali Metal,https://en.wikipedia.org/wiki/Rubidium
Strontium,Sr,38,87.62,Alkaline Earth Metal,https://en.wikipedia.org/wiki/Strontium
Yttrium,Y,39,88.906,Transition Metal,https://en.wikipedia.org/wiki/Yttrium
Zirconium,Zr,40,91.224,Transition Metal,https://en.wikipedia.org/wiki/Zirconium
Niobium,Nb,41,92.906,Transition Metal,https://en.wikipedia.org/wiki/Niobium
Molybdenum,Mo,42,95.96,Transition Metal,https://en.wikipedia.org/wiki/Molybdenum
Technetium,Tc,43,98.0,Transition Metal,https://en.wikipedia.org/wiki/Technetium
Ruthenium,Ru,44,101.07,Transition Metal,https://en.wikipedia.org/wiki/Ruthenium
Rhodium,Rh,45,102.906,Transition Metal,https://en.wikipedia.org/wiki/Rhodium
Palladium,Pd,46,106.42,Transition Metal,https://en.wikipedia.org/wiki/Palladium
Silver,Ag,47,107.868,Transition Metal,https://en.wikipedia.org/wiki/Silver
Cadmium,Cd,48,112.411,Transition Metal,https://en.wikipedia.org/wiki/Cadmium
Indium,In,49,114.818,Metal,https://en.wikipedia.org/wiki/Indium
Tin,Sn,50,118.71,Metal,https://en.wikipedia.org/wiki/Tin
Antimony,Sb,51,121.76,Metalloid,https://en.wikipedia.org/wiki/Antimony
Tellurium,Te,52,127.6,Metalloid,https://en.wikipedia.org/wiki/Tellurium
Iodine,I,53,126.904,Halogen,https://en.wikipedia.org/wiki/Iodine
Xenon,Xe,54,131.293,Noble Gas,https://en.wikipedia.org/wiki/Xenon
Cesium,Cs,55,132.905,Alkali Metal,https://en.wikipedia.org/wiki/Cesium
Barium,Ba,56,137.327,Alkaline Earth Metal,https://en.wikipedia.org/wiki/Barium
Lanthanum,La,57,138.905,Lanthanide,https://en.wikipedia.org/wiki/Lanthanum
Cerium,Ce,58,140.116,Lanthanide,https://en.wikipedia.org/wiki/Cerium
Praseodymium,Pr,59,140.908,Lanthanide,https://en.wikipedia.org/wiki/Praseodymium
Neodymium,Nd,60,144.242,Lanthanide,https://en.wikipedia.org/wiki/Neodymium
Promethium,Pm,61,145.0,Lanthanide,https://en.wikipedia.org/wiki/Promethium
Samarium,Sm,62,150.36,Lanthanide,https://en.wikipedia.org/wiki/Samarium
Europium,Eu,63,151.964,Lanthanide,https://en.wikipedia.org/wiki/Europium
Gadolinium,Gd,64,157.25,Lanthanide,https://en.wikipedia.org/wiki/Gadolinium
Terbium,Tb,65,158.925,Lanthanide,https://en.wikipedia.org/wiki/Terbium
Dysprosium,Dy,66,162.5,Lanthanide,https://en.wikipedia.org/wiki/Dysprosium
Holmium,Ho,67,164.93,Lanthanide,https://en.wikipedia.org/wiki/Holmium
Erbium,Er,68,167.259,Lanthanide,https://en.wikipedia.org/wiki/Erbium
Thulium,Tm,69,168.934,Lanthanide,https://en.wikipedia.org/wiki/Thulium
Ytterbium,Yb,70,173.054,Lanthanide,https://en.wikipedia.org/wiki/Ytterbium
Lutetium,Lu,71,174.967,Lanthanide,https://en.wikipedia.org/wiki/Lutetium
Hafnium,Hf,72,178.49,Transition Metal,https://en.wikipedia.org/wiki/Hafnium
Tantalum,Ta,73,180.948,Transition Metal,https://en.wikipedia.org/wiki/Tantalum
Wolfram,W,74,183.84,Transition Metal,https://en.wikipedia.org/wiki/Wolfram
Rhenium,Re,75,186.207,Transition Metal,https://en.wikipedia.org/wiki/Rhenium
Osmium,Os,76,190.23,Transition Metal,https://en.wikipedia.org/wiki/Osmium
Iridium,Ir,77,192.217,Transition Metal,https://en.wikipedia.org/wiki/Iridium
Platinum,Pt,78,195.084,Transition Metal,https://en.wikipedia.org/wiki/Platinum
Gold,Au,79,196.967,Transition Metal,https://en.wikipedia.org/wiki/Gold
Mercury,Hg,80,200.59,Transition Metal,https://en.wikipedia.org/wiki/Mercury
Thallium,Tl,81,204.383,Metal,https://en.wikipedia.org/wiki/Thallium
Lead,Pb,82,207.2,Metal,https://en.wikipedia.org/wiki/Lead
Bismuth,Bi,83,208.98,Metal,https://en.wikipedia.org/wiki/Bismuth
Polonium,Po,84,210.0,Metal,https://en.wikipedia.org/wiki/Polonium
Astatine,At,85,210.0,Halogen,https://en.wikipedia.org/wiki/Astatine
Radon,Rn,86,222.0,Noble Gas,https://en.wikipedia.org/wiki/Radon
Francium,Fr,87,223.0,Alkali Metal,https://en.wikipedia.org/wiki/Francium
Radium,Ra,88,226.0,Alkaline Earth Metal,https://en.wikipedia.org/wiki/Radium
Actinium,Ac,89,227.0,Actinide,https://en.wikipedia.org/wiki/Actinium
Thorium,Th,90,232.038,Actinide,https://en.wikipedia.org/wiki/Thorium
Protactinium,Pa,91,231.036,Actinide,https://en.wikipedia.org/wiki/Protactinium
Uranium,U,92,238.029,Actinide,https://en.wikipedia.org/wiki/Uranium
Neptunium,Np,93,237.0,Actinide,https://en.wikipedia.org/wiki/Neptunium
Plutonium,Pu,94,244.0,Actinide,https://en.wikipedia.org/wiki/Plutonium
Americium,Am,95,243.0,Actinide,https://en.wikipedia.org/wiki/Americium
Curium,Cm,96,247.0,Actinide,https://en.wikipedia.org/wiki/Curium
Berkelium,Bk,97,247.0,Actinide,https://en.wikipedia.org/wiki/Berkelium
Californium,Cf,98,251.0,Actinide,https://en.wikipedia.org/wiki/Californium
Einsteinium,Es,99,252.0,Actinide,https://en.wikipedia.org/wiki/Einsteinium
Fermium,Fm,100,257.0,Actinide,https://en.wikipedia.org/wiki/Fermium
Mendelevium,Md,101,258.0,Actinide,https://en.wikipedia.org/wiki/Mendelevium
Nobelium,No,102,259.0,Actinide,https://en.wikipedia.org/wiki/Nobelium
Lawrencium,Lr,103,262.0,Actinide,https://en.wikipedia.org/wiki/Lawrencium
Rutherfordium,Rf,104,261.0,Transition Metal,https://en.wikipedia.org/wiki/Rutherfordium
Dubnium,Db,105,262.0,Transition Metal,https://en.wikipedia.org/wiki/Dubnium
Seaborgium,Sg,106,266.0,Transition Metal,https://en.wikipedia.org/wiki/Seaborgium
Bohrium,Bh,107,264.0,Transition Metal,https://en.wikipedia.org/wiki/Bohrium
Hassium,Hs,108,267.0,Transition Metal,https://en.wikipedia.org/wiki/Hassium
Meitnerium,Mt,109,268.0,Transition Metal,https://en.wikipedia.org/wiki/Meitnerium
Darmstadtium,Ds,110,271.0,Transition Metal,https://en.wikipedia.org/wiki/Darmstadtium
Roentgenium,Rg,111,272.0,Transition Metal,https://en.wikipedia.org/wiki/Roentgenium
Copernicium,Cn,112,285.0,Transition Metal,https://en.wikipedia.org/wiki/Copernicium
Nihonium,Nh,113,284.0,Metal,https://en.wikipedia.org/wiki/Nihonium
Flerovium,Fl,114,289.0,Metal,https://en.wikipedia.org/wiki/Flerovium
Moscovium,Mc,115,288.0,Metal,https://en.wikipedia.org/wiki/Moscovium
Livermorium,Lv,116,292.0,Metal,https://en.wikipedia.org/wiki/Livermorium
Tennessine,Ts,117,295.0,Halogen,https://en.wikipedia.org/wiki/Tennessine
Oganesson,Og,118,294.0,Noble Gas,https://en.wikipedia.org/wiki/Oganesson

Output: ss

I am still learning, so thanks for all the suggestions :)

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Your lanthanide indices are incorrect? \$\endgroup\$
    – Reinderien
    Jan 6, 2022 at 18:16
  • \$\begingroup\$ @Reinderien Oops, yea you are right. A silly mistake it was \$\endgroup\$ Jan 6, 2022 at 18:21
  • \$\begingroup\$ Heya @CoolCloud, what is it that you'd like feedback on here? Anything and everything? \$\endgroup\$
    – ades
    Jan 10, 2022 at 18:23
  • \$\begingroup\$ @ades Yea sure, anything :) \$\endgroup\$ Jan 10, 2022 at 18:56

1 Answer 1

2
\$\begingroup\$

Let's start with what I think will be the easiest bit: the data format of your save-files.

CSV

Csv is quite old and not very common nowadays. I'd say most Python developers use json most of the time, but also yaml, toml, and xml are common. That would convert your "database" from

Element,Symbol,AtomicNumber,AtomicMass,Type,WikiLink
Hydrogen,H,1,1.007,Nonmetal,https://en.wikipedia.org/wiki/Hydrogen
...

to

[
  {
    "Element": "Hydrogen",
    "Symbol": "H",
    "Atomic Number": 1,
    "Atomic Mass": 1.007,
    "Type": "Nonmetal",
    "Wiki Link": "https://en.wikipedia.org/wiki/Hydrogen"
  },
...
]

which is way more readable, and will be very easy to deal with in Python. For example:

>>> import json
>>> with open("elements.json", "r") as f:
...   data = json.load(f)
...
>>> data
[{'Element': 'Hydrogen', 'Symbol': 'H', 'Atomic Number': 1, 'Atomic Mass': 1.007, 'Type': 'Nonmetal', 'Wiki Link': 'https://en.wikipedia.org/wiki/Hydrogen'}, {'Element': 'Carbon'}]

Now, as you can see this is essentially the same data structure in Python as it is in json. So I'm gonna make my own life a little easier and just keep this as a variable in a Python-file for now, but you don't have to do that.

Structure and requirements

I think that your application should be packaged. How you'd typically structure a Python package is like this:

.
├── README.rst
├── periodic_table
│   ├── __init__.py
│   ├── elements.py
│   ├── loader.py
│   └── widgets.py
└── requirements.txt

1 directory, 6 files

One thing is that it displays purpose - you can see that all of these files are part of the package periodic_table. It also creates a namespace. The file __init__.py will also mean that you can refer to the modules in this package by changing from

from widgets import Element, Key, Demo

to

from periodic_table.widgets import Element, Demo, Key

and now the reader immediately knows where these functions come from. Personally I think that an even better suggestion is:

from periodic_table import widgets
# and then use widgets.Element, etc.

But I concede that I don't abide by this strongly.

But that's not packaged!

I cheated a little just now - I showed a directory with just a requirements.txt file. Better than this would be to configure the package. I'll be modern and will skip setup.py, and I will also be very minimal with the metadata. We now have:

.
├── README.rst
├── periodic_table
│   ├── __init__.py
│   ├── elements.py
│   ├── loader.py
│   └── widgets.py
├── pyproject.toml
└── setup.cfg

1 directory, 7 files

pyproject.toml

You can consider this file "boilerplate code" for packages and just copy the content without deeper understanding (for now).

[build-system]
requires = [
  "setuptools>=42",
  "wheel"
]
build-backend = "setuptools.build_meta"

setup.cfg

This is where you define all your metadata for the package. It doesn't all matter if you just want to make it installable, but if you wanted to publish something on PyPI you should make some effort.

[metadata]
name = periodic-table
version = 1.0.0

[options]
packages = find:
python_requires = >=3.6
setup_requires =
  wheel
install_requires =
  tkinter
  pandas

[options.entry_points]
console_scripts =
  show-periodic-table = periodic_table:main

With all of this done, your package would be installable with pip! You can then from the directory just type pip install . and the package periodic_table would be installed in that environment. And this also allows us to define commands to call on certain functions!

And this sort of segways us into the next point - your current entry-point is confusing.

if __name__ == "__main__": and other tings

When I look at your filenames my interpretation is that main.py is the entry-point, yet at the bottom of widgets.py you have:

if __name__ == '__main__':
    root = tk.Tk()

    Element(root,'Hydrogen').pack(padx=5,pady=20)
    Key(root,e_type='Halogen').pack()
    Demo(root,'Hydrogen').pack()
    
    root.mainloop()

Here I can clearly see that widgets.py is "executable" (interpretable?) and that this file is intended to start a tkinter loop. I'm guessing that this just an artifact from when you first learnt to use tkinter, but this is basically what we should have done in your main.py file, so let's do that.

main.py

The point behind having if __name__ == "__main__": is that it means that the code wrapped in that condition only will be called upon if the current file is executed directly, like this:

$ python3 main.py  # We want this to run our program

But not be loaded if the module is imported, like this:

from periodic_table import main  # We do not want this to run our program

Entry-points

What I did in setup.cfg (go back and have a look at the end of the file) is to also set up an additional way to call on your program. The lines

console_scripts =
  show-periodic-table = periodic_table:main

declare that there should be an alias set that you can use from your shell (on Linux and macOS, not sure how this works from PowerShell and others) called show-periodic-table, and that this should call on the function main in periodic_table/__init__.py. You could also call the function show_periodic_table and place it in widgets.py if you wanted to - in that case the line would be show-periodic-table = periodic_table.widgets:show_periodic_table.

To fit your code with this, we only really have to rename main.py into __init__.py, add like two lines, and indent most of the existing lines. I will, however, make one other tiny change just to try and keep the code almost working. We can keep widgets.py exactly as it is today, but remember how I mentioned I'd just store the otherwise-json data in a variable? That allows me to get rid of the loader while also simplifying packaging (I'm not gonna explain what I mean - this review has to end some time today...). So we have:

.
├── README.rst
├── periodic_table
│   ├── __init__.py
│   ├── constants.py
│   └── widgets.py
├── pyproject.toml
└── setup.cfg

1 directory, 6 files

constants.py

ELEMENTS = [
  {
    "Element": "Hydrogen",
    "Symbol": "H",
    "Atomic Number": 1,
    "Atomic Mass": 1.007,
    "Type": "Nonmetal",
    "Wiki Link": "https://en.wikipedia.org/wiki/Hydrogen"
  },
...
]

SEC_LAYOUT = [['x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x' ,'x'],
...
]

...

__init__.py

This is now where we have our entry point. I've left it called just main, but an even more fitting name would probably be show_periodic_table.

import tkinter as tk
from tkinter import ttk

from periodic_table.widgets import Element, Key, Demo
from periodic_table.constants import MAIN_LAYOUT, SEC_LAYOUT, ELEMENTS, TYPES


def main():
    root = tk.Tk()
    root.title('Periodic Table')

    # Frame for entire table
    table = tk.Frame(root)
    table.pack()

    ...

    # Placing the representation in the frame
    Demo(repr_tab, elements[0]).grid(row=i,column=j+1,rowspan=5)

    root.mainloop()

And I think I'll let that wrap this session up. The code would need some other modifications to work entirely (since we now already have a list of element dictionaries instead of having to load from csv), but I'll leave that for you to figure out.

\$\endgroup\$
2
  • \$\begingroup\$ Great explanation, I think this is the professional way of releasing packages because I have seen such formats on many big GitHub repos. I will look into these and implement these. And also, about the if __name__ == '__main__': part, I said those not for it to be the entry point, but just as for testing, just running that file will give a way on how each of those widgets would look. And got a bit confused, should I make a json file or put the data inside constants.py? Thanks for the answer :) \$\endgroup\$ Jan 11, 2022 at 2:44
  • 1
    \$\begingroup\$ Just start with the constants.py for now - the reason I didn't say to make a json-file for that data is because it would make the packaging a little bit more tricky; you'd have to define that the json-file also should be packaged, and then you need to know how to refer to its installed location. \$\endgroup\$
    – ades
    Jan 11, 2022 at 8:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.