【问题标题】:Can I declare Python class fields outside the constructor method?我可以在构造函数方法之外声明 Python 类字段吗?
【发布时间】:2020-04-07 18:13:35
【问题描述】:

我是Python的新手(我来自Java),我对类字段有以下疑问。

考虑这样的代码:

class Toy():
    def __init__(self, color, age):
        self.color = color
        self.age = age

action_figure = Toy('red', 10)

好的,做了什么很清楚也很简单:

它定义了一个 Toy 类。在构造方法中定义了两个字段以及如何设置字段值。最后(在“main”中)创建了一个新的 Toy 实例,在构造函数调用中传递字段的值。

好的,很清楚......但我有一个疑问。在 Java 中定义相同的类我会这样做:

public class Toy {
    private String color;
    private int age;

    // CONSTRUCTOR:
    public Dog(String color, int age) {
        this.color = color;
        this.age = age;
    }
}

好的,它很相似,但我发现了一个很大的不同。在我的 Java conde 中,我将类字段声明为构造函数方法之外的变量。在 Python 中,我直接在构造函数方法中定义类字段。所以这意味着在 Java 中我可以声明 n 个类字段并使用构造函数方法只初始化这些字段的一个子集,例如这样的:

public class Toy {
    private String color;
    private int age;
    private String field3;
    private String field4;
    private String field5;

    // CONSTRUCTOR:
    public Dog(String color, int age) {
        this.color = color;
        this.age = age;
    }
}

我还有 field3field4field5 字段,这些字段不会由我的构造函数初始化(以防我可以设置它们第二次使用 setter 方法获取值)。

我可以在 Python 中做类似的事情吗?我可以在构造方法之外声明类字段吗?

【问题讨论】:

标签: python python-3.x python-object


【解决方案1】:

正如您所说,这不是核心 python 语言的一部分。

这对我来说也是一个缺失的功能,原因如下:

  • 可读性/可维护性:使用经典的python在构造函数或其他地方动态定义属性的方式,在阅读代码时不清楚什么是“合同”(或至少预期的鸭合同) 的对象。

  • 紧凑性:仅使用self.<foo> = <foo> 创建长构造函数并不是最有趣的,而且需要的字段越多,需要编写的行数就越多

  • 扩展字段契约的能力,例如在默认值可变的情况下添加默认值工厂,或添加值验证器

  • 能够创建混合类,即实现某些功能的类依赖于某些字段,但不强制使用任何构造函数。

这就是我创建pyfields 的原因。有了这个库,每个字段都被定义为一个类成员:

from pyfields import field
from typing import List

class Toy:
    color: str = field(doc="The toy's color, a string.")
    age: int = field(doc="How old is this Toy. An integer number of years.")
    field3: str = field(default='hello', check_type=True)
    field4: List[str] = field(default_factory=lambda obj: ['world'])
    field5: str = field(default=None, 
                        validators={'should be 1-character long': lambda x: len(x) == 1})

    def __init__(self, color, age):
        self.color = color
        self.age = age


t = Toy(color='blue', age=12)
print(t.field3 + ' ' + t.field4[0])
print(t.field5 is None)
t.field5 = 'yo'

产量

hello world
True
Traceback (most recent call last):
  ...
valid8.entry_points.ValidationError[ValueError]: Error validating [Toy.field5=yo]. InvalidValue: should be 1-character long. Function [<lambda>] returned [False] for value 'yo'.

请注意,我使用上面的 python 3.7+ 类型提示语法,但 pyfields 与旧版本(python 2、python 3.5)兼容,请参阅 documentation

您甚至可以通过auto-creating the constructorusing @autofields to generate the field() calls for you 进一步简化此示例。 pyfields 还提供了一个 @autoclass,因此您甚至可以轻松生成其他类行为,例如字符串表示、相等、转换为 dict 等。请参阅 autoclass doc

请注意,pyfields 的灵感来自于 attrs 等巨头,但其独特之处在于它保留了隔离原则。所以它不会在你背后摆弄__init____setattr__。因此,这允许您的字段在集合中进行验证(不仅在构造函数中),还可以开发优雅的混合类定义字段和方法,但没有构造函数。

【讨论】:

  • 能否请反对者解释他/她认为这个答案没有回答原始问题的原因?这将对其他读者有所帮助。
【解决方案2】:

python 中的类与 c++/java 中的类根本不同,因为 c++/java 类具有固定的数据结构和大小(字节),因为每个属性都是在所有方法之外(通常作为私有变量)声明或定义的,但在python 一切都在运行中(动态输入)。

实例变量(任何带有self. 的变量)只能在方法中定义。在构造函数与其他方法中定义属性的选择是关于其他人能够快速理解您的代码/数据结构(尽管由于动态性调用 python 类数据结构是不合适的)

作为动态性的示例,您甚至可以在运行时向类甚至实例添加新方法和属性:

class A:
    pass

在运行时向类添加东西(这些将被添加到类的所有现有和未来实例中):

A.key = val

def f(self):
    return 0

A.myfunction = f
a = A()
a.myfunction()
# 0

在运行时向单个实例添加内容:

a=A()
a.attr='something'

def f(self):
    return 0

a.fun=f.__get__(a)
a.fun()
# 0

【讨论】:

  • “实例变量(任何带有 self. 的变量)只能在方法中定义。”:这并不完全正确。首先,不需要调用第一个方法参数self。其次,您可以在任何方法之外添加实例属性:a = A(); a.myprop = 3 可以正常工作。
【解决方案3】:

由于 Python 是动态类型的,因此您无需事先声明变量,而是在运行时初始化它们。这也意味着您不必向构造函数添加实例属性,但您可以在以后随时添加它们。事实上,您可以为任何对象添加属性,包括类对象本身。向构造函数添加实例属性主要是一致性和可读性的问题。

添加到类定义中的数据属性在 Python 中称为类属性(我不懂 Java,但我相信,这对应于静态变量)。这很有用,例如跟踪所有类实例:

class Dog:
  lineage = {'Phylum':'Chordata', 'Class':'Mammalia', 'Species':'Canis lupus'}
  all_dogs = []

  def __init__(self, fur_color, tail_length):
    self.fur_color = fur_color
    self.tail_length = tail_length
    self.all_dogs.append(self)  # class attributes can be accessed via the instance

Bello = Dog('white',50)
print(Dog.all_dogs)
print(Dog.[0].fur_color)

【讨论】:

  • 你不仅不需要,你也不能。注意一些 dybamii 语言确实有变量声明,例如JavaScript,但 Python 没有
【解决方案4】:

在 python 中,你可以这样做:

class Toy():
    def__init__(self, color, age):
        self.color = color
        self.age = age

    def another_method(self, f):
         self.field3 = f + 4
         return self.field3

但为了清晰起见,通常建议(更多参数:https://stackoverflow.com/a/38378757/4709400)在构造函数中初始化所有实例变量,所以你会这样做:

class Toy():
    def__init__(self, color, age):
        self.color = color
        self.age = age
        self.field3 = None
        self.field4 = 0 # for instance
        self.field5 = "" # for instance

    def another_method(self, f):
         self.field3 = f + 4
         return self.field3

【讨论】:

  • 好的,现在有办法在任何方法之外将类字段声明为变量吗? (类似于我的 Java 示例中所做的)
  • @AndreaNobili Python 根本没有变量声明
  • 最接近 Python 必须声明(而不是定义)实例属性的方法是数据类采用的方法,但即便如此,使用 class 语句自动生成 @987654325 也只是稍有不同@方法。
【解决方案5】:

在 Python 中确实不需要这个。您可以在需要时将 Java 中所谓的“实例变量”添加到类的实例中:

class Person:

    def __init__(self, name):
        self.name = name

    def get_a_job(self):
        self.job = "Janitor"
        print(f"{self.name} now has a job!")

p1 = Person("Tom")
p2 = Person("Bob")

p1.get_a_job()
print(p1.job)

print(p2.job)

输出:

Tom now has a job!
Janitor
Traceback (most recent call last):
  File "...", line 17, in <module>
    print(p2.job)
AttributeError: 'Person' object has no attribute 'job'
>>> 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 2014-03-04
    相关资源
    最近更新 更多