1

I want to init id as a concatenation of phrase and type

from enum import Enum
from typing import NamedTuple

class WORD_TYPE(str, Enum):
    APPROVED = 'approved'
    FORBIDDEN = 'forbidden'
    RISKY = 'risky'

class WordItem(NamedTuple):
    phrase: str
    type: WORD_TYPE

    id: str = f'{phrase}_{type.name.lower()}'

so each time when I specify phrase and type I want to have an id automatically.

word_item = WordItem(phrase='phrase', type=WORD_TYPE.FORBIDDEN)
asssert word_item.id == 'phrase_forbidden'

What is the best way of doing that? Maybe there is some way of doing this using https://github.com/ericvsmith/dataclasses?

4
  • can you elaborate on "WORD_TYPE" what type is this? Commented Oct 3, 2018 at 11:51
  • it's an Enum. updated my question Commented Oct 3, 2018 at 11:53
  • what Python ver. are you using? 3.7? Commented Oct 3, 2018 at 11:59
  • 3.6, but I can use dataclasses Commented Oct 3, 2018 at 12:03

2 Answers 2

2

That's the kind of problem the special method __new__ is supposed to solve. Unfortunately, NamedTuple prevents its overwriting inside its definition.

But you can overwrite it outside the definition with a decorator:

def set_default(attr, func):
    def set_new(typ):
        old = typ.__new__
        sig = inspect.signature(old)
        def _new(cls, *args, **kwargs):
                bound = sig.bind_partial(cls, *args, **kwargs).arguments
                if not attr in bound:
                    bound[attr] = func(*args, **kwargs)
                # print(bound)           # uncomment for debug traces
                return old(**bound)
        typ.__new__ = _new
        return typ
    return set_new

@set_default('id', lambda phrase, typ: f'{phrase}_{typ.name.lower()}')
class WordItem(NamedTuple):
    phrase: str
    type: WORD_TYPE
    id: str = None

You can then use WordItem:

>>> w = WordItem('a', WORD_TYPE.APPROVED)
>>> w
WordItem(phrase='a', type=<WORD_TYPE.APPROVED: 'approved'>, id='a_approved')
>>> w2 = WordItem('b', WORD_TYPE.APPROVED, 'c')
>>> w2
WordItem(phrase='b', type=<WORD_TYPE.APPROVED: 'approved'>, id='c')

But:

  • NamedTuple prevents overwriting of __new__ for a reason
  • this tries to implement a feature that is explicitely prohibited

If you can, you should avoid this trick. It works fine in my 3.6 version, but it could break in a future version of NamedTuple, or be superseded by a native implementation...

Sign up to request clarification or add additional context in comments.

2 Comments

thanks, for the detailed explanation. what will be the best way of doing this without using NamedTuple?
@petrush: unsure of it. Do you really need the typing module here? If you don't, simply using collections.namedtuple could be simpler.
1

You can define the definition inside an __ init __ method . Something like this :

class WordItem(object):
...   def __init__(self, first_name, last_name):
...     self.full_name = f'{first_name}_{last_name}'
...
>>> word_item = WordItem('abc','xyz')
>>> assert word_item.full_name == 'abc_xyz'

7 Comments

In Python 3.7 you don't need the __init__()
@MosheSlavin Care to elaborate?
I'm not too familiar with Python 3.7 you can read here realpython.com/python-data-classes
@MosheSlavin could you explain please how to do this without init? I'm using 3.6 version, but I can from dataclasses import dataclass
I haven't tried it yet just read about it... it works only in 3.7
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.