Getting variable name in of some duck-type classes

90 views Asked by At

I have a Language class as such:

class _Language:
    def __init__(self, name, bRightToLeft=False):
        self.name = name
        self.bRightToLeft = bRightToLeft

    def isRightToLeft(self):
        return self.bRightToLeft

    def getName(self):
        return self.name

class Language:
    EN = _Language("English")
    AF = _Language("Afrikaans")
    SQ = _Language("Albanian")  

And I create a Language object as such:

l1 = Language.EN

After some processing with the english object, I would like to retrieve its "subtype", i.e. EN. For instance:

print l1

[out]:

EN

I have tried adding __repr__ or a __str__ in the Language class but I'm not getting EN when i print l1:

class Language:
    EN = _Language("English")

    AF = _Language("Afrikaans")
    SQ = _Language("Albanian")   

    def __str__(self):
        return self.__name__

[out]:

Language

How could I access the variable name such that when I print l1 I get EN?

3

There are 3 answers

0
kindall On BEST ANSWER

Any individual _Language instance has no idea what two-letter name you have given it in the Language namespace (or anywhere else, for that matter). So you have two possibilities:

(1) Have each instance store that information, on pain of having to repeat yourself:

class _Language:

    def __init__(self, name, code, rtl=False):
        self.name = name
        self.code = code.upper()
        self.rtl  = rtl

    def __str__(self):
        return self.code

# ...

class Language:
    EN = _Language("English", "EN")

Of course, you can reduce the repetition by providing a class method on Language to create and register _Language instances, rather than creating them at class definition time:

class Language:
    @classmethod
    def add(cls, name, code, rtl=False):
        settatr(cls, code, _Language(name, code, rtl))

Language.add("English", "EN")
Language.add("Afrikaans", "AF")
Language.add("Albanian", "SQ")

This is probably the solution I'd favor personally.

(2) Have your _Language.__str__ method search the Language namespace to find out what name it's known by there:

def __str__(self):
    for k, v in Language.__dict__.iteritems():
        if v is self:
           return k

In this case, you could store the result so it only needs to be looked up once:

class _Language:

# ...

    code = None
    def __str__(self):
        if self.code:
            return self.code
        for k, v in Language.__dict__.iteritems():
            if v is self:
               self.code = k
               return k
0
b4hand On

Overriding, __str__ is the right approach but self.__name__ is the class name and not the member name, which is why you always see "Language".

In order to do what you want, you'll need to do some metaprogramming.

One approach would be to pass the "short" names in as arguments to the constructor. A more hacky approach would be to use the internal dict of the Language class object.

0
abarnert On

As b4hand points out, __name__ is the name of the type, which obviously isn't what you want. Objects don't know the names of variables they've been assigned to. If you think about it, how could they? The same object could be assigned to 20 different variables, or none (maybe the only place it exists is as a member of a set).

Passing the codes into the _Language constructor, as in kindall's answer, is obviously the cleanest solution… but it requires some repetition. Which, besides being tedious, introduces an opportunity for errors—and the kind of stupid typo errors that are the most painful to debug.

So, is there a way we could solve that? Sure. Just add the codes after construction:

class _Language:
    def __init__(self, name, bRightToLeft=False):
        self.name = name
        self.bRightToLeft = bRightToLeft

    def isRightToLeft(self):
        return self.bRightToLeft

    def getName(self):
        return self.name

    def __str__(self):
        return self.code

class Language:
    EN = _Language("English")
    AF = _Language("Afrikaans")
    SQ = _Language("Albanian")

for code, language in inspect.getmembers(Language):
    if code.isalpha() and code.isupper():
        language.code = code

But, while we're at it, we could also dispense with all that repetition of _Language:

class Language:
    EN = "English"
    AF = "Afrikaans"
    SQ = "Albanian"

for code, language in inspect.getmembers(Language):
    if code.isalpha() and code.isupper():
        _language = _Language(language)
        _language.code = code
        setattr(Language, code, _language)

Although I think this might be nicer if you either used a dict or an enum.Enum instead of a class full of nothing but class attributes.

Or, if you want to get fancy, there are all kinds of Enum recipes that show how to create a custom enum metaclass that will use the existing magic that lets Enum values know their names, and also let you cram other attributes into them as you do in the _Language class.