简体   繁体   中英

Defining a class “alias” within the class definition

I'd like to be able to assign aliases to class names, and somehow define the aliases within the class body. So, for example, rather than doing this:

class C(object):
    pass
C1 = C

I'd like to be able to do this:

class C(object):
    __aliases__ = ['C1']

or something of the sort.

My objective is to make the information about the aliases contained within the class definition so that auto-completion works in the right environments and any kind of introspection will reveal the class' aliases (so, for example, documentation can be made to include these aliases.)

I'm guessing I can play games with jumping to the containing frame or whatever, but I'd just like to avoid any ugly trickery if that's possible.

The question is, is this possible?

Seems like this would be a good job to handle with a metaclass:

class AKA(type):
    """ 'Also Known As' metaclass to create aliases for a class. """
    def __new__(cls, classname, bases, attrs):
        print('in AKA.__new__')
        class_ = type(classname, bases, attrs)
        globals().update({alias: class_ for alias in attrs.get('aliases', [])})
        return class_

class C(object):
    __metaclass__ = AKA
    aliases = 'C1', 'C2'

print(C)          # <class '__main__.C'>
print(C.aliases)  # ('C1', 'C2')
print(C1)         # <class '__main__.C'>
print(C2)         # <class '__main__.C'>

Note: In Python 3.x the syntax for specifying a metaclass is different and would need to be:

class C(object, metaclass=AKA):
    aliases = 'C1', 'C2'

Here's yet another way to do something similar with a class decorator—but the aliases aren't specified within the class body like you wanted. It's very similar to @BrenBarn's answer , except the aliases are decorator arguments—which I think might be more desirable for some types of usage (and it seems more explicit since it makes them more conspicuous that way).

import sys

def aliases(*pseudonyms):
    def aliaser(cls):
        namespace = sys._getframe(1).f_globals  # Caller's globals.
        namespace.update({alias: cls for alias in pseudonyms})
        cls.aliases = pseudonyms
        return cls
    return aliaser

if __name__ == '__main__':
    @aliases('SN', 'FUBAR')
    class LongName(object):
        pass

    print(SN)                # <class '__main__.LongName'>
    print(LongName.aliases)  # ('SN', 'FUBar')


It's a bad idea, but it can be done with an abuse of class decorators:

def aliaser(cls):
    for alias in cls.aliases:
        globals()[alias] = cls
    return cls

@aliaser
class LongName(object):
    aliases = ['LN', 'Ugh']

>>> LN
<class '__main__.LongName'>

This is an abuse because the point of decorators is to modify the object they decorate, but this one is used only for its global side-effect of assigning additional names in the global namespace that point to the same class.

Use at your own risk! ;-)

Defining a python class “alias” within the class definition

I'm just responding to the above problem statement. Martineau's non-accepted answer is close to what I'd do to provide a simple alias. But maybe we don't really want an alias, maybe we really just want to take steps towards getting rid of a bad name.

Naming is hard. Sometimes we wind up with names we'd like to change.

I'm looking at some library code with unfortunately named classes that need deprecation. For example:

class BadC: # Bad name, but changing it could break others!
    """Great implementation!"""

A multitude of aliases is not helpful, however. Multiple names that point to the same object mean more to learn, and make your code more complex than it needs to be. The bad names need to eventually go away.

I propose the following sort of change as a solution to warn and not break your users:

class GoodC:
    """Great implementation!"""


class BadC(GoodC):
    """deprecated, use GoodC instead!"""
    def __init__(self, *args, **kwargs):
        import warnings
        warnings.warn(
          "BadC is deprecated, import and use GoodC instead!",
          DeprecationWarning)
        return super().__init__(*args, **kwargs)

Autocomplete will automatically know these names, and introspection ( help(BadC) ) will immediately reveal that you should stop using BadC .

Now you can start replacing BadC with GoodC in the rest of your code.

>>> GoodC()
<__main__.GoodC object at 0x7f0d0c521c88>

and when you or your users use BadC, they'll get their warning:

>>> BadC()
__main__:4: UserWarning: The BadC name is deprecated, import and use GoodC instead!
<__main__.BadC object at 0x7f0d0c521cc0>

And still be able to continue using it:

>>> BadC()
<__main__.BadC object at 0x7f0d0c521c88>

Tests will usually show these warnings, or users can enable them, from the docs

In the ideal case, the code will have a suitable test suite, and the test runner will take care of implicitly enabling all warnings when running tests (the test runner provided by the unittest module does this).

In less ideal cases, applications can be checked for use of deprecated interfaces by passing -Wd to the Python interpreter (this is shorthand for -W default ) or setting PYTHONWARNINGS=default in the environment. This enables default handling for all warnings, including those that are ignored by default. To change what action is taken for encountered warnings you can change what argument is passed to -W (eg -W error ). See the -W flag for more details on what is possible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM