首页 > 解决方案 > Python:如何初始化 NamedTuple 类

问题描述

我想创建一个类,又不想在new之后修改它的属性,所以我选择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()

标签: python-3.xtkinterpython-decoratorsnamedtuple

解决方案


装饰者

您可以使用装饰器来帮助您。

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__并放置您的 init 函数__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

以上所有属性的类都是只读的(对于 2 和 3,可以从类中更改,但不能从实例中更改),不接受其他新属性。


推荐阅读