使用自定义元类指定类的键入

时间:2019-03-23 14:33:41

标签: pycharm metaclass mypy python-typing

在一个很棒的系统上,该系统使用类似枚举的方式替换Django选择(http://musings.tinbrain.net/blog/2017/may/15/alternative-enum-choices/),我有一个项目使用带有自定义元类的类,该类允许我执行list(MyChoices)(在类本身)以获取所有枚举选择的列表。代码的相关部分看起来像这样:

class MetaChoices(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        return OrderedDict()

    def __new__(mcs, name, bases, attrs):
        _choices = OrderedDict()
        for attr_name, value in list(attrs.items()):
            ...do things...
        return type.__new__(mcs, name, bases, dict(attrs))

    def __iter__(cls):
        return iter(cls._choices.items())


class Choices(metaclass=MetaChoices):
    pass

class IceCreamFlavor(Choices):
    STRAWBERRY = ('strawberry', 'Fruity')
    CHOCOLATE = 'chocolate'

list(IceCreamFlavor)
# [('strawberry', 'Fruity'), ('chocolate', Chocolate')

代码运行良好已有一段时间了,但是现在我已经打开了键入功能(在这种情况下,使用PyCharm的类型检查器,但也在寻找常规解决方案),尽管IceCreamFlavor仍未标记为可迭代它是从其元类将cls定义为具有__iter__方法的类派生的。有谁知道一个可以证明Choices类本身是可迭代的解决方案吗?

1 个答案:

答案 0 :(得分:1)

我建议将代码修正为MyPy正确的(通过先添加注释文件* .pyi的Pytype进行检查比较容易)。也许deleteRange()中当前的键入问题可能导致检查器中忽略了元类方法。如果它没有帮助,并且简化示例也失败了,那么可以真正报告它。这不是一个大问题,因为它可以由代码中的抽象基类来提示。


您唯一的键入问题是方法/* */ package de.test.stackoverflow; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringUtils; public class Intervals { public static final class Range { final int from; final int to; public Range(int from, int to) { this.from = from; this.to = to; } public int getFrom() { return from; } public int getTo() { return to; } @Override public String toString() { return String.format("[%d, %d)", from, to); } } final SortedSet<Integer> intervalBoundaries = new java.util.TreeSet<>(); public List<Range> getRanges() { final List<Range> res = new java.util.ArrayList<>(); if (intervalBoundaries.size() > 1) { final List<Integer> tmpBounds = new java.util.ArrayList<>(intervalBoundaries); for (int i = 0; i < tmpBounds.size() - 1; i++) { res.add(new Range(tmpBounds.get(i), tmpBounds.get(i + 1))); } } return res; } public void insertBoundary(Integer bound) { // duplicates in sets are automatically removed intervalBoundaries.add(bound); } public void insertRange(Integer from, Integer to) { intervalBoundaries.add(from); intervalBoundaries.add(to); } public void insertRange(Range x) { insertRange(x.getFrom(), x.getTo()); } public void deleteRange(Range x) { final Collection<Integer> boundsToDelete = new java.util.LinkedHashSet<>(); for (Integer intervalBoundary : intervalBoundaries) { if (intervalBoundary >= x.getFrom() && intervalBoundary < x.getTo()) { boundsToDelete.add(intervalBoundary); } } intervalBoundaries.removeAll(boundsToDelete); if (x.getTo() < intervalBoundaries.last()) { insertBoundary(x.getTo()); } if (x.getFrom() > intervalBoundaries.last()) { insertBoundary(x.getFrom()); } } public static void main(String[] args) { Intervals i = new Intervals(); i.insertRange(new Range(1, 4)); i.insertRange(new Range(4, 9)); i.insertRange(new Range(9, 12)); System.out.println("Given collection: " + StringUtils.join(i.getRanges(), ", ")); final Range insertRange = new Range(5, 8); System.out.println("insert: " + insertRange); i.insertRange(insertRange); System.out.println("Result: " + StringUtils.join(i.getRanges(), ", ")); final Range deleteRange = new Range(4, 5); System.out.println("delete: " + deleteRange); i.deleteRange(deleteRange); System.out.println("Result: " + StringUtils.join(i.getRanges(), ", ")); final Range deleteRange2 = new Range(3, 9); System.out.println("delete: " + deleteRange2); i.deleteRange(deleteRange2); System.out.println("Result: " + StringUtils.join(i.getRanges(), ", ")); final Range deleteRange3 = new Range(2, 15); System.out.println("delete: " + deleteRange3); i.deleteRange(deleteRange3); System.out.println("Result: " + StringUtils.join(i.getRanges(), ", ")); } } 中的属性__iter__对于检查者而言似乎是未定义的,因为它不是由__iter__透明地分配的。可以通过添加一行来进行注释:

_choices

它对Pytype完全有效,并且带有注释的MyPY当然也对其进行了检查。


如果没有帮助,则可以通过抽象基类声明可迭代的接口:

attrs['_choices'] = ...

报告此问题的可能的简化示例是:

class MetaChoices(type):
    _choices = None  # type: dict   # written as comment for Python >= 3.5
    # _choices: dict                # use this line if only Python >= 3.6

我在原始文章中报告了另一个与该问题无关的小错误。