fluent Python 第 2 版第 24 章 __init_subclass 与 meta class 的__setattr__区别 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
zhouyin
V2EX    Python

fluent Python 第 2 版第 24 章 __init_subclass 与 meta class 的__setattr__区别

  •  
  •   zhouyin 363 天前 1649 次点击
    这是一个创建于 363 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有么有人看了并且看懂了 我发现一个不懂的地方 为什么通过__init__subclass 设置描述符 跟 通过元类 new 方法设置描述符生成的类 会在 set value 时 有区别

    第一种会触发实例的__setattr__

    第二种不会触发

    最大的区别在 Field 类 setattr(instance, self.storage_name, value) 与 instance.dict[self.name] = value

    这个是基于元类:

    from collections.abc import Callable from typing import Any, NoReturn, get_type_hints # tag::CHECKED_FIELD[] class Field: def __init__(self, name: str, constructor: Callable) -> None: if not callable(constructor) or constructor is type(None): raise TypeError(f'{name!r} type hint must be callable') self.name = name self.storage_name = '_' + name # <1> self.cOnstructor= constructor def __get__(self, instance, owner=None): if instance is None: # <2> return self return getattr(instance, self.storage_name) # <3> def __set__(self, instance: Any, value: Any) -> None: if value is ...: value = self.constructor() else: try: value = self.constructor(value) except (TypeError, ValueError) as e: type_name = self.constructor.__name__ msg = f'{value!r} is not compatible with {self.name}:{type_name}' raise TypeError(msg) from e setattr(instance, self.storage_name, value) # <4> # end::CHECKED_FIELD[] # tag::CHECKED_META[] class CheckedMeta(type): def __new__(meta_cls, cls_name, bases, cls_dict): # <1> print(cls_dict.get('__slots__')) if '__slots__' not in cls_dict: # <2> print ("\n\n\nin __new__\n\n\n") slots = [] type_hints = cls_dict.get('__annotations__', {}) # <3> for name, constructor in type_hints.items(): # <4> field = Field(name, constructor) # <5> cls_dict[name] = field # <6> slots.append(field.storage_name) # <7> cls_dict['__slots__'] = slots # <8> return super().__new__( meta_cls, cls_name, bases, cls_dict) # <9> # end::CHECKED_META[] # tag::CHECKED_CLASS[] class Checked(metaclass=CheckedMeta): __slots__ = () # skip CheckedMeta.__new__ processing @classmethod def _fields(cls) -> dict[str, type]: return get_type_hints(cls) def __init__(self, **kwargs: Any) -> None: print(super().__class__.__name__) for name in self._fields(): value = kwargs.pop(name, ...) setattr(self, name, value) if kwargs: self.__flag_unknown_attrs(*kwargs) def __flag_unknown_attrs(self, *names: str) -> NoReturn: plural = 's' if len(names) > 1 else '' extra = ', '.join(f'{name!r}' for name in names) cls_name = repr(self.__class__.__name__) raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}') def _asdict(self) -> dict[str, Any]: return { name: getattr(self, name) for name, attr in self.__class__.__dict__.items() if isinstance(attr, Field) } def __repr__(self) -> str: kwargs = ', '.join( f'{key}={value!r}' for key, value in self._asdict().items() ) return f'{self.__class__.__name__}({kwargs})' class Movie(Checked): title: str year: int box_office: float movie = Movie(title='The Godfather', year=1972, box_office=137) print(movie) print(movie.title) print(type(CheckedMeta),type(Checked),type(Movie)) # end::MOVIE_DEMO[] 

    下面是基于__init_subclass

    from collections.abc import Callable # <1> from typing import Any, NoReturn, get_type_hints class Field: def __init__(self, name: str, constructor: Callable) -> None: # <2> if not callable(constructor) or constructor is type(None): # <3> raise TypeError(f'{name!r} type hint must be callable') self.name = name self.cOnstructor= constructor def __set__(self, instance: Any, value: Any) -> None: if value is ...: # <4> value = self.constructor() else: try: value = self.constructor(value) # <5> except (TypeError, ValueError) as e: # <6> type_name = self.constructor.__name__ msg = f'{value!r} is not compatible with {self.name}:{type_name}' raise TypeError(msg) from e instance.__dict__[self.name] = value # <7> # end::CHECKED_FIELD[] # tag::CHECKED_TOP[] class Checked: @classmethod def _fields(cls) -> dict[str, type]: # <1> return get_type_hints(cls) def __init_subclass__(subclass) -> None: # <2> super().__init_subclass__() # <3> for name, constructor in subclass._fields().items(): # <4> setattr(subclass, name, Field(name, constructor)) # <5> def __init__(self, **kwargs: Any) -> None: for name in self._fields(): # <6> value = kwargs.pop(name, ...) # <7> setattr(self, name, value) # <8> if kwargs: # <9> self.__flag_unknown_attrs(*kwargs) # <10> # end::CHECKED_TOP[] # tag::CHECKED_BOTTOM[] def __setattr__(self, name: str, value: Any) -> None: # <1> if name in self._fields(): # <2> cls = self.__class__ descriptor = getattr(cls, name) descriptor.__set__(self, value) # <3> else: # <4> self.__flag_unknown_attrs(name) def __flag_unknown_attrs(self, *names: str) -> NoReturn: # <5> plural = 's' if len(names) > 1 else '' extra = ', '.join(f'{name!r}' for name in names) cls_name = repr(self.__class__.__name__) raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}') def _asdict(self) -> dict[str, Any]: # <6> return { name: getattr(self, name) for name, attr in self.__class__.__dict__.items() if isinstance(attr, Field) } def __repr__(self) -> str: # <7> kwargs = ', '.join( f'{key}={value!r}' for key, value in self._asdict().items() ) return f'{self.__class__.__name__}({kwargs})' class Movie(Checked): title: str year: int box_office: float movie = Movie(title='The Godfather', year=1972, box_office=137) print(movie.title) print(movie) try: # remove the "type: ignore" comment to see Mypy error movie.year = 'MCMLXXII' # type: ignore except TypeError as e: print(e)
    第 1 条附言    363 天前
    问题描述不准确

    CheckedMeta 如果有__setattr__方法 给描述符赋值时 也会触发该方法

    现在的问题是

    为什么

    1.继承自 CheckedMeta 的类 也就是通过元类 __new__ 方法设置描述符生成的类 没有__setattr__方法 给描述符赋值时 会自动调用描述符的__set__方法

    2.通过__init_subclass 方法设置描述符生成的类 没有__setattr__方法 给描述符赋值时 不会自动调用描述符的__set__方法
    2 条回复    2024-12-26 13:19:23 +08:00
    zhouyin
        1
    zhouyin  
    OP
       363 天前
    问题描述不准确

    CheckedMeta 如果有__setattr__方法 给描述符赋值时 也会触发该方法

    现在的问题是

    为什么

    1.继承自 CheckedMeta 的类 也就是通过元类 __new__ 方法设置描述符生成的类 没有__setattr__方法 给描述符赋值时 会自动调用描述符的__set__方法

    2.通过__init_subclass 方法设置描述符生成的类 没有__setattr__方法 给描述符赋值时 不会自动调用描述符的__set__方法
    zhouyin
        2
    zhouyin  
    OP
       363 天前
    我好像找到了原因
    1.___init_subclass__ 通过 setattr 给实例增加描述符属性
    2.继承自自定义元类的类 通过元类的__new__方法 的第四个参数 class_dict 给实例增加描述符属性 具体的原因肯定在更底层代码 不把用户自定义描述符加到 class_dict 赋值时就不会触发描述符的__set__方法
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5670 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 02:51 PVG 10:51 LAX 18:51 JFK 21:51
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86