Reputation: 7978
I want to create a class, and I don’t want its attributes to be modified after new, so I choose NamedTuple
,
But I hope that It can do something immediately after initialize,
So I hope to I can override the __init__
method,
but if I do this, I will encounter AttributeError: Cannot overwrite NamedTuple attribute __init__
.
Is there any elegant code that can do it?
My actual case is to initialize the style of ttk
, as follows.
import tkinter as tk
from tkinter import ttk
from typing import NamedTuple
class TTKStyle(NamedTuple):
LF_NORMAL = f'Normal.TLabelframe'
def init_style(self):
style = ttk.Style()
style.configure(self.LF_NORMAL, background='#FFFF00')
style.configure(f'{self.LF_NORMAL}.Label', foreground='red', background='blue', font=('courier', 15, 'bold'))
root = tk.Tk()
ttk_style = TTKStyle()
ttk_style.init_style() # <-- I don't want to write this line.
lf_exif = ttk.LabelFrame(root, text='EXIF Tag', style=ttk_style.LF_NORMAL)
lf_exif.pack()
tk.Label(lf_exif, text='ExifVersion').pack()
root.mainloop()
Upvotes: 1
Views: 1430
Reputation: 7978
You can use the decorator to help you.
from typing import NamedTuple, Type
def init_namedtuple(init_func_name):
def wrap(class_obj: Type[NamedTuple]):
def new_instance(*args, **kwargs):
instance_obj = class_obj(*args, **kwargs)
init_func = getattr(instance_obj, init_func_name)
if init_func:
init_func()
return instance_obj
return new_instance
return wrap
@init_namedtuple('init_style')
class TTKStyle(NamedTuple):
...
or you can use normal class and add __slots__
and put your init function on __init__
directly (see class Person3
), for example:
from typing import NamedTuple
class Person(NamedTuple):
NAME: str
SCIENTIFIC_NAME = 'Homo sapiens'
class Person2:
__slots__ = ()
NAME: str
SCIENTIFIC_NAME = 'Homo sapiens'
class Person3:
__slots__ = ()
NAME: str
SCIENTIFIC_NAME = 'Homo sapiens'
def __init__(self, name: str):
self.__class__.NAME = name
...
class Person4:
__slots__ = ('_name', '_scientific_name')
def __init__(self, name: str):
self._name = name # Technically, the way is not really read-only, but a conventional is so.
self._scientific_name = 'Homo sapiens'
@property
def name(self):
return self._name
@property
def scientific_name(self):
return self._scientific_name
for person in (Person('Carson'), Person('Carson2')):
print(person.NAME) # output: Carson, Carson2
for person in (Person4('Carson3'), Person4('Carson4')):
print(person.name) # output: Carson3, Carson4
if "it's ok but weird":
unknown_person = Person2()
unknown_person.__class__.NAME = '???'
print(unknown_person.NAME)
for person in [Person3('Marry'), Person3('Marry2')]:
# BE CAREFUL! IF YOU USE THE CLASS ATTRIBUTE, THEN ALL THE INSTANCES IS SHARED THIS VARIABLE.
print(person.NAME) # both output is: Marry2
The class of all the above attributes is read-only (for 2 and 3, you can change it from the class but not change from instance) and not accept other new attributes.
Upvotes: 2