草庐IT

python - 如何在元类中键入提示动态设置的类属性?

coder 2023-08-21 原文

当我动态设置一个类的属性时:

from typing import TypeVar, Generic, Optional, ClassVar, Any

class IntField:
    type = int

class PersonBase(type):
    def __new__(cls):
        for attr, value in cls.__dict__.items():
            if not isinstance(value, IntField):
                continue
            setattr(cls, attr, value.type())
        return cls

class Person(PersonBase):
    age = IntField()

person = Person()

print(type(Person.age)) # <class 'int'>
print(type(person.age)) # <class 'int'>
person.age = 25 # Incompatible types in assignment (expression has type "int", variable has type "IntField")

age 属性的类型将是 int 类型,但 MyPy 不能遵循该类型。

有没有办法让 MyPy 理解?

Django 已经实现了它:

from django.db import models

class Person(models.Model):
    age = models.IntegerField()

person = Person()
print(type(Person.age)) # <class 'django.db.models.query_utils.DeferredAttribute'>
print(type(person.age)) # <class 'int'>
person.age = 25  # No error

Django 是如何做到这一点的?

最佳答案

由于您在类上定义了字段,实用的方法是对该字段进行类型提示。请注意,您必须告诉 mypy 不要检查行本身。

class Person(PersonBase):
    age: int = IntField()  # type: ignore

这是最少的变化,但相当不灵活。


您可以使用带有假签名的辅助函数来创建自动类型化的通用提示:

from typing import Type, TypeVar


T = TypeVar('T')


class __Field__:
    """The actual field specification"""
    def __init__(self, *args, **kwargs):
        self.args, self.kwargs = args, kwargs


def Field(tp: Type[T], *args, **kwargs) -> T:
    """Helper to fake the correct return type"""
    return __Field__(tp, *args, **kwargs)  # type: ignore


class Person:
    # Field takes arbitrary arguments
    # You can @overload Fields to have them checked as well
    age = Field(int, True, object())

这就是 attrs 库提供其遗留提示的方式。这种风格允许隐藏注释的所有魔法/技巧。


由于元类可以检查注释,因此无需将类型存储在 Field 上。您可以为元数据使用裸字段,并为类型使用注释:

from typing import Any


class Field(Any):  # the (Any) part is only valid in a .pyi file!
    """Field description for Any type"""


class MetaPerson(type):
    """Metaclass that creates default class attributes based on fields"""
    def __new__(mcs, name, bases, namespace, **kwds):
        for name, value in namespace.copy().items():
            if isinstance(value, Field):
                # look up type from annotation
                field_type = namespace['__annotations__'][name]
                namespace[name] = field_type()
        return super().__new__(mcs, name, bases, namespace, **kwds)


class Person(metaclass=MetaPerson):
    age: int = Field()

这就是 attrs 提供其 Python 3.6+ 属性的方式。它既通用又符合注释风格。请注意,这也可以与常规基类而不是元类一起使用。

class BasePerson:
     def __init__(self):
         for name, value in type(self).__dict__.items():
             if isinstance(value, Field):
                 field_type = self.__annotations__[name]
                 setattr(self, name, field_type())


class Person(BasePerson):
    age: int = Field()

关于python - 如何在元类中键入提示动态设置的类属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54946944/

有关python - 如何在元类中键入提示动态设置的类属性?的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  5. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  6. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  7. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  8. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  9. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  10. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

随机推荐