如何保持python项目模块化?

如何保持python项目模块化?,python,Python,上下文 我最近一直在从事一个python项目,发现模块化非常重要。例如,您创建了一个具有某些属性的类,以及使用这些属性的代码行,如 a = A() print("hi"+a.imA) 如果要将A类的imA修改为其他类型,则必须修改print语句。就我而言,我不得不这么做很多次。这既烦人又费时。get/set方法可以解决这个问题,但我听说get/set不是“好python”。那么,如果不使用get和set方法,您将如何解决这个问题呢?首先,使用 print(f"hi {a.imA}") # P

上下文

我最近一直在从事一个python项目,发现模块化非常重要。例如,您创建了一个具有某些属性的类,以及使用这些属性的代码行,如

a = A()
print("hi"+a.imA)
如果要将
A
类的
imA
修改为其他类型,则必须修改print语句。就我而言,我不得不这么做很多次。这既烦人又费时。get/set方法可以解决这个问题,但我听说get/set不是“好python”。那么,如果不使用get和set方法,您将如何解决这个问题呢?

首先,使用

print(f"hi {a.imA}")  # Python 3.6+

而不是

print("hi"+a.imA)
这样,将在每个参数上自动调用
str

然后在所有类中定义一个
\uuuu str\uuuu
函数,这样
print
ing任何类都能正常工作

class A:
    def __init__(self):
        self._member_1 = "spam"

    def __str__(self):
        return f"A(member 1: {self._member_1})"

第一点:如果使用字符串格式而不是字符串串联,您将省去不少麻烦,例如:

print("hi {}".format(a.imA))
诚然,最终结果可能是您所期望的,也可能不是,这取决于
a.imA
type如何实现
\uuu str\uuuu()
\uu repr\uuu()
,但至少这不会破坏代码

wrt/getter和setter,它们确实被认为是非语法的,因为python对有很强的支持,并且有一个简单的泛型实现

注意:实际上,当一个普通的公共属性足够时,系统地使用实现属性和getter/setter(无论是显式的还是隐式的,就像计算属性一样)被认为是非语法的,这被认为是非Pyththic的,因为您可以总是将一个普通属性转换成计算的属性而不破坏客户端代码(假设当然,您不改变属性的类型和语义)-这是早期java类不可能的,例如Simultalk、C++或Java。(Smalltalk实际上有点特殊,但这是另一个话题)

在您的案例中,如果重点是在不破坏API的情况下更改存储值的类型,那么简单的规范解决方案是使用
属性
委托给实现属性:

之前:

class Foo(object):
    def __init__(self, bar):
        # `bar`  is expected to be the string representation of an int.
        self.bar = bar

    def frobnicate(self, val):
        return (int(self.bar) + val) / 2
之后:

class Foo(object):
    def __init__(self, bar):
        # `bar`  is expected to be the string representation of an int.
        self.bar = bar

    # but we want to store it as an int
    @property
    def bar(self):
        return str(self._bar)

    @bar.setter
    def bar(self, value):
        self._bar = int(value)

    def frobnicate(self, val):
        # internally we use the implementation attribute `_bar`
        return (self._bar + val) / 2
现在,您将该值作为int存储在内部,但公共接口(几乎)完全相同-唯一的区别是,将无法传递到
int()
的内容传递到预期的位置(当您设置它时),而不是在最意外的位置断开(当您调用
.frobnite()

现在请注意,更改公共属性的类型就像更改getter的返回类型(或setter参数的类型)-在这两种情况下,您都违反了约定-因此,如果您真正想要更改
A.imA
的类型,则getter和properties都无法解决您的问题-getter和setter(或Python计算属性中的getter和setter)只能保护您不受实现更改的影响


编辑:哦,是的:这与模块化无关(即编写易于阅读、测试、维护和最终重用的解耦、自包含的代码),但与封装无关(其目的是使公共接口能够适应实现更改).

看看。getter和setter是如何解决你的界面破坏合同的问题的?无论如何,你可以使用属性。@Eddie这是个好主意。我要告诉我的老板,我们正在用typescript重新实现我们的500+kloc(工作)python基码,因为“它是类型安全的,IDE可以很容易地找到并替换名称”,所以我们可以“安全地”只要我们愿意,就随时中断类公共API…您不必为字符串格式明确定义
\uuu str\uu()
,Python已经提供了一个默认实现(即使对于python2“旧式”类也是如此)。虽然我完全同意使用字符串格式而不是串联,但这并不能回答整个问题。但它调用默认的
\uuuu repr\uuu
,在大多数情况下,它不会打印您想要的内容。属性无助于使用简单的
打印(my_对象。my_成员)干净地打印所有内容
,无论
my_成员
my_对象
的类型如何。您声明“在所有类中定义一个str函数,以便打印任何类都能正常工作”,这意味着(或“可以很容易地解释为”)它将以其他方式中断-情况并非如此,因此我的注释的第一部分。
class Foo(object):
    def __init__(self, bar):
        # `bar`  is expected to be the string representation of an int.
        self.bar = bar

    # but we want to store it as an int
    @property
    def bar(self):
        return str(self._bar)

    @bar.setter
    def bar(self, value):
        self._bar = int(value)

    def frobnicate(self, val):
        # internally we use the implementation attribute `_bar`
        return (self._bar + val) / 2