簡體   English   中英

Class 屬性在子類中不存在

[英]Class attribute which is not present on subclasses

主要是為了好玩,為了幫助我正在編寫的解析器中的一些人體工程學,我非常想覆蓋一個類' __getattr__而不對其子類做同樣的事情。 為了解釋,一些代碼:

class AttrMeta(type):
    def __getattr__(self, name):
        if name == "A":
            return "hi there."

class Main(metaclass=AttrMeta):
    pass

class Subclass(Main):
    pass

print(Main.A)  # => hi there.
print(Subclass.A) # => hi there.
print(Main().A) # => AttributeError: 'Main' object has no attribute 'A'
print(Subclass().A) # => AttributeError: 'Subclass' object has no attribute 'A'

這很正常,但我希望Subclass不繼承__getattr__ 為此,我嘗試的第一件事是在AttrMeta中覆蓋__new__ ,如下所示:

# in AttrMeta...
def __new__(mcs, name, bases, namespace, **kwargs):
    if bases:
        return type.__new__(type, name, bases, namespace, **kwargs) 
    return type.__new__(mcs, name, bases, namespace, **kwargs)

這里的想法是從Main的子類中剝離元類,這些子類是通過查看bases來檢測的。 在創建class Subclass(Main)時,這導致了一些無限遞歸,這是有道理的——你怎么會有這樣一個損壞的 inheritance 鏈?

我的下一個想法是手動刪除__getattr__

# in AttrMeta...
def __new__(mcs, name, bases, namespace, **kwargs):
    new_cls = type.__new__(mcs, name, bases, namespace, **kwargs)
    if bases:
        del new_cls.__getattr__
    return new_cls

好吧,回想起來很明顯你不能這樣做:你得到一個AttributeError: __getattr__ 那是因為Subclass.__getattr__綁定到AttrMeta - 事實上, "__getattr__" not in dir(Subclass)成立。

這時,我對自己說,嘿。 Tim Peters 說需要元類的人都知道這一點,而我根本不是那些人中的一員。 事實上,我根本“不需要”編寫這個解析器,我只是想,你知道,在隔離期間得到一點消遣。 也許我應該通過分配給__getattr__上的 __getattr__ 來做到這一點,然后看看會發生什么。 所以,我嘗試了:

class Main:
    pass

class Subclass(Main):
    pass

def __getattr__(self, name):
    if name == "A":
        return "hi there."

Main.__getattr__ = __getattr__

Main.A # raises AttributeError: type object 'Main' has no attribute 'A'

在這一點上,我已經沒有想法了。 我可能會放棄並允許Subclass擁有屬性 getter。 我不想在每個Subclass上覆蓋__getattr__因為有很多子類,但我很好奇是否有任何想法。 感謝您閱讀這篇長文。

啊,我想出了至少一種可行的方法。 這個想法是像這樣添加一個新的元類:

class AttrMeta(type):
    def __new__(mcs, name, bases, namespace, **kwargs):
        if bases:
            mcs = NoMoreMeta
        return type.__new__(mcs, name, bases, namespace, **kwargs)

    def __getattr__(self, name):
        if name == "A":
            return "hi there."


class NoMoreMeta(AttrMeta):
    def __getattr__(self, name):
        if name == "A":
            raise AttributeError(name)


class Main(metaclass=AttrMeta):
    pass


class Subclass(Main):
    pass

然后Main.A像上面一樣工作,但Subclass.A會引發相應的錯誤。

不能在 Python 中將屬性或方法設置為“不繼承” - 您可以僅在子類上隱藏先前存在的方法或屬性,並且可以使用它來注入會引發 AttributeError 的替換。

在這種情況下,由於 Python 3.6,您甚至不需要元類 - 您可以使用__init_subclass__方法,該方法在創建子類(但不是當前類)時運行,將__getattr__替換為會引發:

class Main:
    def __getattr__(self, attr):
        if attr == "A":
            return "A"
        raise AttributeError()

    def __init_subclass__(cls, *args, **kw):
        super().__init_subclass__(*args, **kw)
        def __getattr__(self, attr):
            # blocks the original getattr
            raise AttributeError(attr)

        # optional check: allow subclasses to implement their own __getattr__ methods:
        if "__getattr__" in cls.__dict__:
            return
        cls.__getattr__ = __getattr__


class SubClass(Main):
    pass

這運行:


In [95]: Main().A                                                                                              
Out[95]: 'A'

In [96]: SubClass().A                                                                                          
---------------------------------------------------------------------------
AttributeError  

但是,您正在將__getattr__添加到元類本身 - (我感到羞恥,我現在才看到它) - 所以動態屬性可以出現在類上,而不僅僅是實例上。

go 它們的更簡單方法是使用某些屬性標記原始“基礎” class,並在元類__getattr__上檢查是否在該基礎 ZA2F2ED4F8EBC2CBB4C21A29DZ40AB6 上調用 getattr

__init_subclass__方法不起作用,因為元類本身是相同的,主 class 子類沒有使用“元類的子類”)




mark = "_AttrBase"

def find_marked(cls):
    for sclass in cls.__mro__:
        if sclass.__dict__.get(mark, False):
            return sclass
    return None

class AttrMeta(type):
    def __new__(mcls, name, bases, namespace, **kwd):
        cls = super().__new__(mcls, name, bases, namespace, **kwd)
        if not find_marked(cls):
            # This line injects a getattr on the instances as well, so 
            # that instances have the same behavior for these as the classes:
            cls.__getattr__ = lambda self, attr: getattr(self.__class__, attr)
            setattr(cls, mark, True)
        return cls

    def __getattr__(cls, attr):
        if find_marked(cls) is not cls:
            raise AttributeError(attr)
        if attr == "A":
            return "A"
        raise AttributeError(attr)

class Main(metaclass=AttrMeta):
    pass

class SubClass(Main):
    pass

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM