Python:如何初始化 NamedTuple 的 class
Python: How can I initialize class of NamedTuple
我想创建一个class,不希望新建后属性被修改,所以选择NamedTuple
,
但我希望它能在初始化后立即做一些事情,
所以我希望我可以覆盖 __init__
方法,
但是如果我这样做,我会遇到AttributeError: Cannot overwrite NamedTuple attribute __init__
.
有没有优雅的代码可以做到?
我的实际案例是初始化ttk
的样式,如下
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()
装饰器
你可以使用装饰器来帮助你。
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):
...
__slots__
或者你可以使用普通的 class 并添加 __slots__
并将你的初始化函数直接放在 __init__
上 (see class Person3
),例如:
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
以上所有属性的class都是read-only(对于2和3,可以从class中改变,但不能从实例中改变)并且不接受其他新的属性。
我想创建一个class,不希望新建后属性被修改,所以选择NamedTuple
,
但我希望它能在初始化后立即做一些事情,
所以我希望我可以覆盖 __init__
方法,
但是如果我这样做,我会遇到AttributeError: Cannot overwrite NamedTuple attribute __init__
.
有没有优雅的代码可以做到?
我的实际案例是初始化ttk
的样式,如下
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()
装饰器
你可以使用装饰器来帮助你。
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):
...
__slots__
或者你可以使用普通的 class 并添加 __slots__
并将你的初始化函数直接放在 __init__
上 (see class Person3
),例如:
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
以上所有属性的class都是read-only(对于2和3,可以从class中改变,但不能从实例中改变)并且不接受其他新的属性。