Python:类属性定义上的代码重复

发布于 2025-01-11 20:31:42 字数 810 浏览 2 评论 0原文

我正在尝试用 python 实现一个简单的 ORM。我面临代码重复问题,但我不知道如何解决。 这是我的项目中的类的简化示例:

class Person:

    TABLE_NAME = 'person'

    FIELDS = [
        ('name', 'VARCHAR(50)'),
        ('age', 'INTEGER')
    ]

    # CODE DUPLICATION: the two next lines shoudl be genereated with FIELDS not hard coded...
    name: str
    age: int

    def __init__(self, **kwargs):
        self.__dict__ = kwargs

    @classmethod
    def create_sql_table(cls):
        # use TABLE_NAME and FIELDS to create sql table
        pass


alice = Person(name='Alice', age=25)

print(alice.name)

如果我删除两行 name: strage: int 我会丢失自动完成功能并收到 mypy 错误在打印行上(错误:人没有属性名称)

但是如果我保留它,则会出现代码重复(每个字段名称我写两次)。

有没有办法避免代码重复(例如通过使用 FIELDS 变量生成这两行)?

或者实现此类避免代码重复的另一种方法(没有 mypy 错误和自动完成丢失)?

I'm trying to implement a simple ORM in python. I'm facing a code duplication issue and I do not know how to solve it.
Here is a simplified example of a class in my project:

class Person:

    TABLE_NAME = 'person'

    FIELDS = [
        ('name', 'VARCHAR(50)'),
        ('age', 'INTEGER')
    ]

    # CODE DUPLICATION: the two next lines shoudl be genereated with FIELDS not hard coded...
    name: str
    age: int

    def __init__(self, **kwargs):
        self.__dict__ = kwargs

    @classmethod
    def create_sql_table(cls):
        # use TABLE_NAME and FIELDS to create sql table
        pass


alice = Person(name='Alice', age=25)

print(alice.name)

If I remove the two lines name: strand age: int I lose auto-completion and I get a mypy error on the print line (Error: Person has no attribute name)

But If I keep it, I have code duplication (I write twice each field name).

Is there a way to avoid the code duplication (by generating this two lines using FIELDS variable for instance) ?

Or another way to implement this class that avoid code duplication (without mypy error and auto-completion loss) ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

七月上 2025-01-18 20:31:42

您可以使用描述符:

from typing import Generic, TypeVar, Any, overload, Union

T = TypeVar('T')

class Column(Generic[T]):
    sql_type: str  # the field type used for this column

    def __init__(self) -> None:
        self.name = ''  # the name of the column

    # this is called when the Person class (not the instance) is created
    def __set_name__(self, owner: Any, name: str) -> None:
        self.name = name  # now contains the name of the attribute in the class

    # the overload for the case: Person.name -> Column[str]
    @overload
    def __get__(self, instance: None, owner: Any) -> 'Column[T]': ...

    # the overload for the case: Person().name -> str
    @overload
    def __get__(self, instance: Any, owner: Any) -> T: ...

    # the implementation of attribute access
    def __get__(self, instance: Any, owner: Any) -> Union[T, 'Column[T]']:
        if instance is None:
            return self
        # implement your attribute access here
        return getattr(instance, f'_{self.name}')  # type: ignore

    # the implementation for setting attributes
    def __set__(self, instance: Any, value: T) -> None:
        # maybe check here that the type matches
        setattr(instance, f'_{self.name}', value)

现在我们可以为每个列类型创建专门化:

class Integer(Column[int]):
    sql_type = 'INTEGER'

class VarChar(Column[str]):
    def __init__(self, size: int) -> None:
        self.sql_type = f'VARCHAR({size})'
        super().__init__()

当您定义 Person 类时,我们可以使用列类型

class Person:
    TABLE_NAME = 'person'

    name = VarChar(50)
    age = Integer()

    def __init__(self, **kwargs: Any) -> None:
        for key, value in kwargs.items():
            setattr(self, key, value)


    @classmethod
    def create_sql_table(cls) -> None:
        print("CREATE TABLE", cls.TABLE_NAME)
        for key, value in vars(cls).items():
            if isinstance(value, Column):
                print(key, value.sql_type)


Person.create_sql_table()

p = Person(age=10)
print(p.age)
p.age = 20
print(p.age)

这将打印:

创建表人员

名称 VARCHAR(50)

年龄整数

10

20

您可能还应该创建一个基 Model 类,其中包含 __init__Person 的类方法code>

您还可以扩展Column 类以允许可为空的列并添加默认值。

Mypy 不会抱怨,并且可以正确地将 Person.name 的类型推断为 str,将 Person.age 推断为 int。

You can use descriptors:

from typing import Generic, TypeVar, Any, overload, Union

T = TypeVar('T')

class Column(Generic[T]):
    sql_type: str  # the field type used for this column

    def __init__(self) -> None:
        self.name = ''  # the name of the column

    # this is called when the Person class (not the instance) is created
    def __set_name__(self, owner: Any, name: str) -> None:
        self.name = name  # now contains the name of the attribute in the class

    # the overload for the case: Person.name -> Column[str]
    @overload
    def __get__(self, instance: None, owner: Any) -> 'Column[T]': ...

    # the overload for the case: Person().name -> str
    @overload
    def __get__(self, instance: Any, owner: Any) -> T: ...

    # the implementation of attribute access
    def __get__(self, instance: Any, owner: Any) -> Union[T, 'Column[T]']:
        if instance is None:
            return self
        # implement your attribute access here
        return getattr(instance, f'_{self.name}')  # type: ignore

    # the implementation for setting attributes
    def __set__(self, instance: Any, value: T) -> None:
        # maybe check here that the type matches
        setattr(instance, f'_{self.name}', value)

Now we can create specializations for each column type:

class Integer(Column[int]):
    sql_type = 'INTEGER'

class VarChar(Column[str]):
    def __init__(self, size: int) -> None:
        self.sql_type = f'VARCHAR({size})'
        super().__init__()

And when you define the Person class we can use the column types

class Person:
    TABLE_NAME = 'person'

    name = VarChar(50)
    age = Integer()

    def __init__(self, **kwargs: Any) -> None:
        for key, value in kwargs.items():
            setattr(self, key, value)


    @classmethod
    def create_sql_table(cls) -> None:
        print("CREATE TABLE", cls.TABLE_NAME)
        for key, value in vars(cls).items():
            if isinstance(value, Column):
                print(key, value.sql_type)


Person.create_sql_table()

p = Person(age=10)
print(p.age)
p.age = 20
print(p.age)

This prints:

CREATE TABLE person

name VARCHAR(50)

age INTEGER

10

20

You should probably also create a base Model class that contains the __init__ and the class method of Person

You can also extend the Column class to allow nullable columns and add default values.

Mypy does not complain and can correctly infer the types for Person.name to str and Person.age to int.

寒江雪… 2025-01-18 20:31:42

好吧,我最终得到了这个

class Person:
    # this is not full, you need to fill other types you use it with the correct relationship
    types = {
        str: 'VARCHAR(50)',
        int: 'INTEGER',
    }  # you should extract that out if you use it elsewhere


    TABLE_NAME = 'person'
    

    # NOTE: the only annotated fields should be these. if you annotate anything else, It will break
    name: str
    age: int

    def __init__(self, **kwargs):
        self.__dict__ = kwargs

    @property
    def FIELDS(cls):
        return [(key, cls.types[value]) for key, value in cls.__annotations__.items()]


alice = Person(name='Alice', age=25)

print(alice.FIELDS)  # [('name', 'VARCHAR(50)'), ('age', 'INTEGER')]

并且

>>> mypy <module>
>>> Success: no issues found in 1 source file

Ok, I ended up with that

class Person:
    # this is not full, you need to fill other types you use it with the correct relationship
    types = {
        str: 'VARCHAR(50)',
        int: 'INTEGER',
    }  # you should extract that out if you use it elsewhere


    TABLE_NAME = 'person'
    

    # NOTE: the only annotated fields should be these. if you annotate anything else, It will break
    name: str
    age: int

    def __init__(self, **kwargs):
        self.__dict__ = kwargs

    @property
    def FIELDS(cls):
        return [(key, cls.types[value]) for key, value in cls.__annotations__.items()]


alice = Person(name='Alice', age=25)

print(alice.FIELDS)  # [('name', 'VARCHAR(50)'), ('age', 'INTEGER')]

And

>>> mypy <module>
>>> Success: no issues found in 1 source file
自我难过 2025-01-18 20:31:42

在 Person 类中尝试在构造函数中添加数据类型

In the class Person try to add data type in constructor

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文