ZutJoe / KoalaHackerNews

Koala hacker news 周报内容 每周二0点左右更新

Home Page:https://zutjoe.github.io/KoalaHackerNews/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

建议:降低函数的与外界类型的耦合度

tower-town opened this issue · comments

commented

main.py

class HackerNewsItems:
    times: list[VideoTime]
    introduces: list[str]
    links: list[str | list[str]]

以及

class VideoInfo

与函数参数形成强耦合,进而每当修改Hacker NewsItem属性将影响全局函数的类型检查。建议将公共属性统一放到一个class的属性里,比如

class HackerNews
   def __init__():
       times: list[VideoTime]
       introduces: list[str]
       links: list[str | list[str]]
       ....
   def method():
       ....
commented

当时好像设计他们的时候好像就是作为参数来的,而不是作为一个对象,我的理解是这样的,这个不是我设计的😂
@GalaxySnail 您来解释解释?

commented

当时好像设计他们的时候好像就是作为参数来的,而不是作为一个对象,我的理解是这样的,这个不是我设计的😂

@GalaxySnail 您来解释解释?

但你是项目作者,可以进行全局改动🤔

emmmm 我有点没太理解,为什么这里会有强耦合(捂脸)能否详细解释一下?按我的理解,dataclass 这种设计就是降低耦合的

commented

但你是项目作者,可以进行全局改动🤔

但是我觉得那个样子好像挺好的。。。。。本来好像就是作为参数,然后dataclass注解也能减少不必要的方法啥的(方便偷懒

commented

emmmm 我有点没太理解,为什么这里会有强耦合(捂脸)能否详细解释一下?按我的理解,dataclass 这种设计就是降低耦合的

有许多函数的参数输入和输出都必须符合Hack NewsItem的属性,可以将公共属性交给class的方法处理,然后整合属性交给一个特定的函数.

commented

但你是项目作者,可以进行全局改动🤔

但是我觉得那个样子好像挺好的。。。。。本来好像就是作为参数,然后dataclass注解也能减少不必要的方法啥的(方便偷懒

你是项目作者,这个仓库你说的算

你的意思是提供一个替代默认 __init__ 的构造函数吗?请问能否写一段示例代码,我不确定我是否理解了你的意思(

commented

你是项目作者,这个仓库你说的算

不能这么说,这里面有人家的代码了,我也要对他的代码负责,那是他的劳动成果

请问能否写一段示例代码

https://docs.python.org/zh-cn/3/library/dataclasses.html

啊我不是这个意思,我是没理解提问者想要把代码改成什么样,所以想让他提供一段示例代码看看他是怎么想的

commented

啊我不是这个意思,我是没理解提问者想要把代码改成什么样,所以想让他提供一段示例代码看看他是怎么想的

哦哦,好的

commented

你的意思是提供一个替代默认 __init__ 的构造函数吗?请问能否写一段示例代码,我不确定我是否理解了你的意思(
提供一个替代默认 __init__ 的构造函数吗
类似的
下面是示例代码

class NameID()
     def __init__(self):
        self.name: str = ""
        self.id: str = ""
    def getNameId(self,name, id):
        self.name = name,
        self.id = id
    def print(self):
        printf(f"name is {self.name}, id is {self.id}")
....
commented

面向对象编程了是嘛😂万恶的java

commented

而不是这样

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
    NameID["name"] = name
    NameID["id"] = id

def print(data:NameID):
        printf(f"name is {data["name"]}, id is {data["id"]}")
class NameID()
     def __init__(self):
        self.name: str = ""
        self.id: str = ""
    def getNameId(self,name, id):
        self.name = name,
        self.id = id
    def print(self):
        printf(f"name is {self.name}, id is {self.id}")
....

嗯……这样的做法有一个问题,那就是实例化后的新对象处于一个“未完全初始化”的状态,每个字段都赋予了一个无意义的空值,那在其它地方处理它的时候就不得不处理字段为空值的情况。而对于 dataclass 来说,一旦对象构造完成,这个对象就是完整的了,可以假设它的每个字段都有一个合适的有意义的值,可以随意操作。

而不是这样

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
    NameID["name"] = name
    NameID["id"] = id

def print(data:NameID):
        printf(f"name is {data["name"]}, id is {data["id"]}")

这里的代码不太对,也许你可以先阅读一下标准库 dataclasses 的文档,链接在上面有。也可以看看这篇文章,里面解释了 dataclass 的核心**。其中一个很重要的**就是,在 init 函数里不要做额外的工作,只是对属性赋值,并且保证对象一旦构造完成就一定处于完全初始化的状态,不会有“初始化了一半”的情况。

类型提示虽然看上去是写在类属性的位置,但描述的不是类属性,而是实例属性,也就是相当于:

class NameID:
    def __init__(self, name: str, id: str):
        self.name = name
        self.id = id
    ...  # 除了 init 之外还自动生成了一些别的方法

要构造一个新的 NameID 对象不需要额外的方法,直接调用 name_id = NameID(name, id) 就可以了。同时也不需要专门的 print 方法,dataclass 自动生成了 __repr__ 特殊方法,所以可以直接调用内置函数 print 来做:

>>> print(name_id)
NameID(name='foo', id='bar')
commented
class NameID()
     def __init__(self):
        self.name: str = ""
        self.id: str = ""
    def getNameId(self,name, id):
        self.name = name,
        self.id = id
    def print(self):
        printf(f"name is {self.name}, id is {self.id}")
....

嗯……这样的做法有一个问题,那就是实例化后的新对象处于一个“未完全初始化”的状态,每个字段都赋予了一个无意义的空值,那在其它地方处理它的时候就不得不处理字段为空值的情况。而对于 dataclass 来说,一旦对象构造完成,这个对象就是完整的了,可以假设它的每个字段都有一个合适的有意义的值,可以随意操作。

class NameID 初始化工作交给main函数调用完成

commented

而不是这样

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
    NameID["name"] = name
    NameID["id"] = id

def print(data:NameID):
        printf(f"name is {data["name"]}, id is {data["id"]}")

这里的代码不太对,也许你可以先阅读一下标准库 dataclasses 的文档,链接在上面有。也可以看看这篇文章,里面解释了 dataclass 的核心**。其中一个很重要的**就是,在 init 函数里不要做额外的工作,只是对属性赋值,并且保证对象一旦构造完成就一定处于完全初始化的状态,不会有“初始化了一半”的情况。

类型提示虽然看上去是写在类属性的位置,但描述的不是类属性,而是实例属性,也就是相当于:

这个不重要因为我在

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
    NameID["name"] = name
    NameID["id"] = id

def print(data:NameID):
        printf(f"name is {data["name"]}, id is {data["id"]}")

加入了 ... 代表省略不重要步骤

commented

要构造一个新的 NameID 对象不需要额外的方法,直接调用 name_id = NameID(name, id) 就可以了。同时也不需要专门的 print 方法,dataclass 自动生成了 repr 特殊方法,所以可以直接调用内置函数 print 来做:

print(name_id)
NameID(name='foo', id='bar')

这只是一个例子的print方法作为示例调用函数,所以不用关心具体的的作用

commented

虽然dataclasses有这样的好处,在本仓库中的大多数调用函数必须完成返回和输入参数与HackerNewsItem的数据处理。但有些函数明明只应该完成提取数据,但其既要处提取数据也要提供数据处理,犯了函数设计的忌讳。

这个不重要因为我在

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
    NameID["name"] = name
    NameID["id"] = id

def print(data:NameID):
        printf(f"name is {data["name"]}, id is {data["id"]}")

加入了 ... 代表省略不重要步骤

不是省略不重要步骤的问题,而是这里的 getNameID 函数写的是错的,没法正常运行,你可以自己尝试一下。(顺便一提,python 编码规范 PEP 8 推荐使用 snake_case 而不是 camelCase)

虽然dataclasses有这样的好处,在本仓库中的大多数调用函数必须完成返回和输入参数与HackerNewsItem的数据处理。但有些函数明明只应该完成提取数据,但其既要处提取数据也要提供数据处理,犯了函数设计的忌讳。

这个我觉得是误解。dataclass 是一种面向对象的**,是以数据为中心,一个 dataclass 对象就是一个数据,你可以在一些函数里生产数据,然后在别的函数消费数据。换言之,是让数据自己作为接口,dataclass 自身即接口,这是一种耦合很松的做法,而且接口定义清晰明确。也正因此,dataclass 的 __init__ 函数是不包括数据处理的逻辑的,它只是简单提取数据而已。数据处理的逻辑在消费 dataclass 的函数里完成。

class NameID 初始化工作交给main函数调用完成

如上所述,dataclass 以数据为中心,数据初始化的工作应该由数据自己完成,而获取数据的函数只需要给 dataclass 提供一些构造 dataclass 所必需的数据,dataclass 就可以自己完成构造,而不应该获取数据的函数来操心如何一步步初始化这个对象。实际上,main 函数需要做的本身也很简单,只要 main 函数有 nameid 的值,那就是一步 name_id = NameID(name, id) 的事罢了。

commented

dataclass 是一种面向对象的**,是以数据为中心,一个 dataclass 对象就是一个数据,你可以在一些函数里生产数据,然后在别的函数消费数据。换言之,是让数据自己作为接口,dataclass 自身即接口,这是一种耦合很松的做法,而且接口定义清晰明确。也正因此,dataclass 的 __init__ 函数是不包括数据处理的逻辑的,它只是简单提取数据而已。数据处理的逻辑在消费 dataclass 的函数里完成。

学到了 佬!Orz

commented

这个不重要因为我在

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
    NameID["name"] = name
    NameID["id"] = id

def print(data:NameID):
        printf(f"name is {data["name"]}, id is {data["id"]}")

加入了 ... 代表省略不重要步骤

不是省略不重要步骤的问题,而是这里的 getNameID 函数写的是错的,没法正常运行,你可以自己尝试一下。(顺便一提,python 编码规范 PEP 8 推荐使用 snake_case 而不是 camelCase)

虽然dataclasses有这样的好处,在本仓库中的大多数调用函数必须完成返回和输入参数与HackerNewsItem的数据处理。但有些函数明明只应该完成提取数据,但其既要处提取数据也要提供数据处理,犯了函数设计的忌讳。

这个我觉得是误解。dataclass 是一种面向对象的**,是以数据为中心,一个 dataclass 对象就是一个数据,你可以在一些函数里生产数据,然后在别的函数消费数据。换言之,是让数据自己作为接口,dataclass 自身即接口,这是一种耦合很松的做法,而且接口定义清晰明确。也正因此,dataclass 的 __init__ 函数是不包括数据处理的逻辑的,它只是简单提取数据而已。数据处理的逻辑在消费 dataclass 的函数里完成。

class NameID 初始化工作交给main函数调用完成

如上所述,dataclass 以数据为中心,数据初始化的工作应该由数据自己完成,而获取数据的函数只需要给 dataclass 提供一些构造 dataclass 所必需的数据,dataclass 就可以自己完成构造,而不应该获取数据的函数来操心如何一步步初始化这个对象。实际上,main 函数需要做的本身也很简单,只要 main 函数有 nameid 的值,那就是一步 name_id = NameID(name, id) 的事罢了。

你的回答并不能没有解决函数的设计的问题,明明数据在公共属性里,一个函数的功能只完成一件事。而在本仓库中的函数大量与公共属性交互。而此例中有大量的函数需要返回指定类型的数据,但明明不是该函数应该做的事情。一些函数值需要完成数据的挂载。

commented

这个不重要因为我在

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
    NameID["name"] = name
    NameID["id"] = id

def print(data:NameID):
        printf(f"name is {data["name"]}, id is {data["id"]}")

加入了 ... 代表省略不重要步骤

不是省略不重要步骤的问题,而是这里的 getNameID 函数写的是错的,没法正常运行,你可以自己尝试一下。(顺便一提,python 编码规范 PEP 8 推荐使用 snake_case 而不是 camelCase)

虽然dataclasses有这样的好处,在本仓库中的大多数调用函数必须完成返回和输入参数与HackerNewsItem的数据处理。但有些函数明明只应该完成提取数据,但其既要处提取数据也要提供数据处理,犯了函数设计的忌讳。

这个我觉得是误解。dataclass 是一种面向对象的**,是以数据为中心,一个 dataclass 对象就是一个数据,你可以在一些函数里生产数据,然后在别的函数消费数据。换言之,是让数据自己作为接口,dataclass 自身即接口,这是一种耦合很松的做法,而且接口定义清晰明确。也正因此,dataclass 的 __init__ 函数是不包括数据处理的逻辑的,它只是简单提取数据而已。数据处理的逻辑在消费 dataclass 的函数里完成。

class NameID 初始化工作交给main函数调用完成

如上所述,dataclass 以数据为中心,数据初始化的工作应该由数据自己完成,而获取数据的函数只需要给 dataclass 提供一些构造 dataclass 所必需的数据,dataclass 就可以自己完成构造,而不应该获取数据的函数来操心如何一步步初始化这个对象。实际上,main 函数需要做的本身也很简单,只要 main 函数有 nameid 的值,那就是一步 name_id = NameID(name, id) 的事罢了。

再次强调不要谈与本仓库无关的概念而非本仓库的具体问题

commented

不要关注于初始化工作,如果想让初始化简单,完全可以将初始化函数放到 __init__

commented

再次强调不要谈与本仓库无关的概念而非本仓库的具体问题

有没有可能在第二部分他解释的就是你所提出的问题

明明数据在公共属性里,一个函数的功能只完成一件事。而在本仓库中的函数大量与公共属性交互

为什么函数不能和公共属性交互?难道函数处理的数据只能通过以参数的形式传递进来吗?

整个项目都是单线程的,没有多线程操作,操作公共数据是不会对数据有误操作的

有大量的函数需要返回指定类型的数据,但明明不是该函数应该做的事情。一些函数值需要完成数据的挂载。

每个函数完成他需要做的事,然后返回相关的数据,我认为是没有问题的

可能有些函数体确实过大,内部可以抽离出来一些其他的函数进行处理,但是抽离出来了也仅仅只是在那个函数里调用一次,我认为是没有必要再一次抽出来,再做一层函数的。由于函数体过大而导致函数过程没有那么容易理解,但这个问题或许可能通过注释解决

commented

可能会出现的问题

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
...

当更改NameID的数据

@dataclass
class NameID:
    name: str
    id: str,
    email: str
....

def getNameID(name, id) -> NameID:
...
# 则需要更改`getNameID`,因为可能`getNameID`并没有处理`email`参数即将`email`参数调回默认值
def getEmail(NameID, email)  -> NameID:
...

而只要

class NameID()
     def __init__(self):
        self.name: str = ""
        self.id: str = ""
    def getNameId(self,name, id):
        self.name = name,
        self.id = id
   def getEmail(self,mail):
       self.email = email
commented

这个问题就是我的问题核心

commented

如果每一个调用NameID不需要处理额外添加数据的返回值,则就没有问题

commented

对了

class NameID()
     def __init__(self):
        self.name: str = ""
        self.id: str = ""
        self.email = ""
commented

如果每一个调用NameID不需要处理额外添加数据的返回值,则就没有问题
这个问题就是我的问题核心

我们确实不需要再添加值了,因为我们所需要的信息就只用这么多

用了dataclass注解之后类我觉得那个类更像是java里的Entity,他们基本上是不会有太大的变化了,而且那两个类确实不会再变化了(确定

class NameID()
     def __init__(self):
        self.name: str = ""
        self.id: str = ""
        self.email = ""
class NameID:
     def __init__(self):
        self.name: str = ""
        self.id: str = ""
        self.email: str = ""
commented

没有问题就ok

再次强调不要谈与本仓库无关的概念而非本仓库的具体问题

回到本仓库的具体问题,我认为目前的代码完美实现了本仓库的需求,没有更改的必要,over。

可能会出现的问题

@dataclass
class NameID:
    name: str
    id: str
....

def getNameID(name, id) -> NameID:
...

当更改NameID的数据

@dataclass
class NameID:
    name: str
    id: str,
    email: str
....

def getNameID(name, id) -> NameID:
...
# 则需要更改`getNameID`,因为可能`getNameID`并没有处理`email`参数即将`email`参数调回默认值
def getEmail(NameID, email)  -> NameID:
...

而只要

class NameID()
     def __init__(self):
        self.name: str = ""
        self.id: str = ""
    def getNameId(self,name, id):
        self.name = name,
        self.id = id
   def getEmail(self,mail):
       self.email = email

那么说明这里的 name, id, email 属性不是绑定在一起的,解决这个问题有两种办法,一种是继承,一种是组合。

继承:

from typing import Self

@dataclass
class NameID:
    name: str
    id: str

@dataclass
class NameIDEmail(NameID):
    email: str

    @classmethod
    def from_nameid_and_email(cls, name_id: NameID, email: str) -> Self:
        return cls(name_id.name, name_id.id, email)

组合:

@dataclass
class NameID:
    name: str
    id: str

@dataclass
class NameIDEmail:
    name_id: NameID
    email: str

(这种情况下,通常组合比继承更好。不过也要看情况。)

或者还有一种办法,如果对于具体需求来说 email 确实是个可选的属性,那可以让它可选:

@dataclass
class NameID:
    name: str
    id: str
    email: str | None = None

这样能让用户之后随时自己设置 email 属性。