关于python:Python-什么情况下会生成-pyc-文件

33次阅读

共计 5319 个字符,预计需要花费 14 分钟才能阅读完成。

成员名不容许反复
这部分我的第一个想法是去管制 dict 中的 key。但这样的形式并不好,__dict__ 范畴大,它蕴含该类的所有属性和办法。而不单单是枚举的命名空间。我在源码中发现 enum 应用另一个办法。通过 prepare 魔术办法能够返回一个类字典实例,在该实例 应用 prepare 魔术办法自定义命名空间,在该空间内限定成员名不容许反复。

# 本人实现
class _Dict(dict):
    def __setitem__(self, key, value):
        if key in self:
            raise TypeError('Attempted to reuse key: %r' % key)
        super().__setitem__(key, value)

class MyMeta(type):
    @classmethod
    def __prepare__(metacls, name, bases):
        d = _Dict()
        return d

class Enum(metaclass=MyMeta):
    pass

class Color(Enum):
    red = 1
    red = 1         # TypeError: Attempted to reuse key: 'red'

再看看 Enum 模块的具体实现:

class _EnumDict(dict):
    def __init__(self):
        super().__init__()
        self._member_names = []
        ...

    def __setitem__(self, key, value):
        ...
        elif key in self._member_names:
            # descriptor overwriting an enum?
            raise TypeError('Attempted to reuse key: %r' % key)
        ...
        self._member_names.append(key)
        super().__setitem__(key, value)

class EnumMeta(type):
    @classmethod
    def __prepare__(metacls, cls, bases):
        enum_dict = _EnumDict()
        ...
        return enum_dict

class Enum(metaclass=EnumMeta):
    ...

模块中的 _EnumDict 创立了 _member_names 列表来存储成员名,这是因为不是所有的命名空间内的成员都是枚举的成员。比方 str__, __new 等魔术办法就不是了,所以这边的 setitem 须要做一些过滤:

def __setitem__(self, key, value):
    if _is_sunder(key):     # 下划线结尾和结尾的,如 _order__
        raise ValueError('_names_ are reserved for future Enum use')
    elif _is_dunder(key):   # 双下划线结尾的, 如 __new__
        if key == '__order__':
            key = '_order_'
    elif key in self._member_names: # 反复定义的 key
        raise TypeError('Attempted to reuse key: %r' % key)
    elif not _is_descriptor(value): # value 得不是描述符
        self._member_names.append(key)
        self._last_values.append(value)
    super().__setitem__(key, value)

模块思考的会更全面。

每个成员都有名称属性和值属性
上述的代码中,Color.red 获得的值是 1。而 eumu 模块中,定义的枚举类中,每个成员都是有名称和属性值的;并且仔细的话还会发现 Color.red 是 Color 的实例。这样的状况是如何来实现的呢。

还是用元类来实现,在元类的 new 中实现,具体的思路是,先创立指标类,而后为每个成员都创立一样的类,再通过 setattr 的形式将后续的类作为属性增加到指标类中,伪代码如下:

def __new__(metacls, cls, bases, classdict):
    __new__ = cls.__new__
    # 创立枚举类
    enum_class = super().__new__()
    # 每个成员都是 cls 的示例,通过 setattr 注入到指标类中
    for name, value in cls.members.items():
        member = super().__new__()
        member.name = name
        member.value = value
        setattr(enum_class, name, member)
    return enum_class

来看下一个可运行的 demo:

class _Dict(dict):
    def __init__(self):
        super().__init__()
        self._member_names = []

    def __setitem__(self, key, value):
        if key in self:
            raise TypeError('Attempted to reuse key: %r' % key)

        if not key.startswith("_"):
            self._member_names.append(key)
        super().__setitem__(key, value)

class MyMeta(type):
    @classmethod
    def __prepare__(metacls, name, bases):
        d = _Dict()
        return d

    def __new__(metacls, cls, bases, classdict):
        __new__ = bases[0].__new__ if bases else object.__new__
        # 创立枚举类
        enum_class = super().__new__(metacls, cls, bases, classdict)

        # 创立成员
        for member_name in classdict._member_names:
            value = classdict[member_name]
            enum_member = __new__(enum_class)
            enum_member.name = member_name
            enum_member.value = value
            setattr(enum_class, member_name, enum_member)

        return enum_class

class MyEnum(metaclass=MyMeta):
    pass

class Color(MyEnum):
    red = 1
    blue = 2

    def __str__(self):
        return "%s.%s" % (self.__class__.__name__, self.name)

print(Color.red)        # Color.red
print(Color.red.name)   # red
print(Color.red.value)  # 1

enum 模块在让每个成员都有名称和值的属性的实现思路是一样的(代码我就不贴了)。EnumMeta.__new__ 是该模块的重点,简直所有枚举的个性都在这个函数实现。

当成员值雷同时,第二个成员是第一个成员的别名
从这节开始就不再应用本人实现的类的阐明了,而是通过拆解 enum 模块的代码来阐明其实现了,从模块的应用个性中能够晓得,如果成员值雷同,后者会是前者的一个别名:

from enum import Enum
class Color(Enum):
    red = 1
    _red = 1

print(Color.red is Color._red)  # True

从这能够晓得,red 和_red 是同一对象。这又要怎么实现呢?

元类会为枚举类创立 member_map 属性来存储成员名与成员的映射关系,如果发现创立的成员的值曾经在映射关系中了,就会用映射表中的对象来取代:

class EnumMeta(type):
    def __new__(metacls, cls, bases, classdict):
        ...
        # create our new Enum type
        enum_class = super().__new__(metacls, cls, bases, classdict)
        enum_class._member_names_ = []               # names in definition order
        enum_class._member_map_ = OrderedDict()      # name->value map

        for member_name in classdict._member_names:
            enum_member = __new__(enum_class)

            # If another member with the same value was already defined, the
            # new member becomes an alias to the existing one.
            for name, canonical_member in enum_class._member_map_.items():
                if canonical_member._value_ == enum_member._value_:
                    enum_member = canonical_member     # 取代
                    break
            else:
                # Aliases don't appear in member names (only in __members__).
                enum_class._member_names_.append(member_name)  # 新成员,增加到_member_names_中

            enum_class._member_map_[member_name] = enum_member
            ...

从代码上来看,即便是成员值雷同,还是会先为他们都创建对象,不过后创立的很快就会被垃圾回收掉了(我认为这边是有优化空间的)。通过与 member_map 映射表做比照,用以创立该成员值的成员取代后续,但两者成员名都会在 member_map 中,如例子中的 red 和 _red 都在该字典,但他们指向的是同一个对象。

属性 member_names 只会记录第一个,这将会与枚举的迭代无关。

能够通过成员值来获取成员

print(Color['red'])  # Color.red  通过成员名来获取成员
print(Color(1))      # Color.red  通过成员值来获取成员

枚举类中的成员都是单例模式,元类创立的枚举类中还保护了值到成员的映射关系 value2member_map :

class EnumMeta(type):
    def __new__(metacls, cls, bases, classdict):
        ...
        # create our new Enum type
        enum_class = super().__new__(metacls, cls, bases, classdict)
        enum_class._value2member_map_ = {}

        for member_name in classdict._member_names:
            value = enum_members[member_name]
            enum_member = __new__(enum_class)

            enum_class._value2member_map_[value] = enum_member
            ...

而后在 Enum 的 new 返回该单例即可:

class Enum(metaclass=EnumMeta):
    def __new__(cls, value):
        if type(value) is cls:
            return value

        # 尝试从 _value2member_map_ 获取
        try:
            if value in cls._value2member_map_:
                return cls._value2member_map_[value]
        except TypeError:
            # 从 _member_map_ 映射获取
            for member in cls._member_map_.values():
                if member._value_ == value:
                    return member

        raise ValueError("%r is not a valid %s" % (value, cls.__name__))

迭代的形式遍历成员
枚举类反对迭代的形式遍历成员,按定义的程序,如果有值反复的成员,只获取反复的第一个成员。对于反复的成员值只获取第一个成员,正好属性 member_names 只会记录第一个:

class Enum(metaclass=EnumMeta):
    def __iter__(cls):
        return (cls._member_map_[name] for name in cls._member_names_)

总结
enum 模块的外围个性的实现思路就是这样,简直都是通过元类黑魔法来实现的。对于成员之间不能做比拟大小但能够做等值比拟。这反而不须要讲,这其实继承自 object 就是这样的,不必额定做什么就有的“个性”了。
总之,enum 模块绝对独立,且代码量不多,对于想晓得元类编程能够浏览一下,教科书式教学,还有单例模式等,值得一读。

以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈,发送“J”即可收费获取,每日干货分享

正文完
 0