我可以在构造方法之外声明 Python class 字段吗?
Can I declare Python class fields outside the constructor method?
我绝对是 Python 的新手(我来自 Java),我对 class 字段有以下疑问。
考虑这样的代码:
class Toy():
def __init__(self, color, age):
self.color = color
self.age = age
action_figure = Toy('red', 10)
好了,做的很清楚也很简单:
它正在定义一个玩具class。在构造方法中定义了两个字段以及如何设置字段值。最后(在“main”中)创建了一个新的 Toy 实例,在构造函数调用中传递字段的值。
好的,清楚了...但我有疑问。在 Java 中定义相同的 class 我做了这样的事情:
public class Toy {
private String color;
private int age;
// CONSTRUCTOR:
public Dog(String color, int age) {
this.color = color;
this.age = age;
}
}
好的,它很相似,但我发现了很大的不同。在我的 Java conde 中,我将 class 字段声明为构造方法之外的变量。在 Python 中,我直接在构造方法中定义 class 字段。所以这意味着在 Java 中我可以声明 n class 字段并使用构造函数方法仅初始化该字段的一个子集,例如这样的东西:
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;
}
}
我还有 field3、field4 和 field5 字段由我的构造函数初始化(以防我可以使用 setter 方法第二次设置它们的值)。
我可以在 Python 中做类似的事情吗?我可以在构造方法之外声明 class 字段吗?
在 Python 中确实不需要这样做。您在 Java 中调用的 "instance variables" 可以随时添加到 class 的实例中:
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'
>>>
在 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
但通常建议为了清晰起见(此处有更多参数:)在构造函数中初始化所有实例变量,因此您可以这样做:
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
类 in python 与 c++/java 的根本区别在于 c++/java class 具有固定的数据结构和大小(字节)因为每个属性都是在所有方法之外声明或定义的(通常作为私有变量),但在 python 中一切都是动态的(动态类型)。
选择在构造函数中定义属性与其他方法相比,其他人能够快速理解您的 code/data 结构(尽管由于动态调用 python classes 数据结构不合适)
作为动态示例您甚至可以在 运行 时间向 classes 甚至实例添加新方法和属性:
class A:
pass
在 运行 时向 class 添加内容(这些内容将添加到 class 的所有现有和未来实例):
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
由于 Python 是动态类型的,您不需要事先声明变量,而是在运行时初始化它们。这也意味着您不必向构造函数添加实例属性,但您可以在以后随时添加它们。事实上,您可以为任何对象添加属性,包括 class 对象本身。给构造函数添加实例属性主要是一致性和可读性的问题。
添加到class定义中的数据属性在Python中称为class属性(我不知道Java,但我相信,这对应到静态变量)。这很有用,例如跟踪所有 class 个实例:
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)
正如您所指出的,这不是核心 python 语言的一部分。
这对我来说也是一个缺失的功能,原因如下:
readability/maintainability:使用 classic python 在构造函数或其他地方动态定义属性的方式,阅读代码时,对象的“契约”(或至少预期的鸭子契约)是什么并不明显。
compacity:仅使用 self.<foo> = <foo>
创建长构造函数并不是最有趣的,您需要的字段越多,您需要的行数就越多写
扩展字段契约的能力,例如在默认值可变的情况下添加默认值工厂,或添加值验证器
创建混入的能力 classes,即 classes 依赖于某些字段实现某些功能,但是不强制使用任何构造函数。
这就是我创建 pyfields
的原因。使用此库,每个字段都定义为 class 成员:
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 constructor or using @autofields
to generate the field()
calls for you. pyfields
also provides an @autoclass
so you can even generate other class behaviours easily such as string representation, equality, conversion to dict, etc. See autoclass doc 进一步简化此示例。
请注意,pyfields
受到 attrs
等巨头的启发,但与众不同之处在于它保留了隔离原则。所以它不会 fiddle 背后有 __init__
或 __setattr__
。因此,这允许您的字段在集合上进行验证(而不仅仅是在构造函数中),并且还可以开发优雅的 mix-in classes 定义字段和方法,但不构造函数。
我绝对是 Python 的新手(我来自 Java),我对 class 字段有以下疑问。
考虑这样的代码:
class Toy():
def __init__(self, color, age):
self.color = color
self.age = age
action_figure = Toy('red', 10)
好了,做的很清楚也很简单:
它正在定义一个玩具class。在构造方法中定义了两个字段以及如何设置字段值。最后(在“main”中)创建了一个新的 Toy 实例,在构造函数调用中传递字段的值。
好的,清楚了...但我有疑问。在 Java 中定义相同的 class 我做了这样的事情:
public class Toy {
private String color;
private int age;
// CONSTRUCTOR:
public Dog(String color, int age) {
this.color = color;
this.age = age;
}
}
好的,它很相似,但我发现了很大的不同。在我的 Java conde 中,我将 class 字段声明为构造方法之外的变量。在 Python 中,我直接在构造方法中定义 class 字段。所以这意味着在 Java 中我可以声明 n class 字段并使用构造函数方法仅初始化该字段的一个子集,例如这样的东西:
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;
}
}
我还有 field3、field4 和 field5 字段由我的构造函数初始化(以防我可以使用 setter 方法第二次设置它们的值)。
我可以在 Python 中做类似的事情吗?我可以在构造方法之外声明 class 字段吗?
在 Python 中确实不需要这样做。您在 Java 中调用的 "instance variables" 可以随时添加到 class 的实例中:
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'
>>>
在 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
但通常建议为了清晰起见(此处有更多参数:
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
类 in python 与 c++/java 的根本区别在于 c++/java class 具有固定的数据结构和大小(字节)因为每个属性都是在所有方法之外声明或定义的(通常作为私有变量),但在 python 中一切都是动态的(动态类型)。
选择在构造函数中定义属性与其他方法相比,其他人能够快速理解您的 code/data 结构(尽管由于动态调用 python classes 数据结构不合适)
作为动态示例您甚至可以在 运行 时间向 classes 甚至实例添加新方法和属性:
class A:
pass
在 运行 时向 class 添加内容(这些内容将添加到 class 的所有现有和未来实例):
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
由于 Python 是动态类型的,您不需要事先声明变量,而是在运行时初始化它们。这也意味着您不必向构造函数添加实例属性,但您可以在以后随时添加它们。事实上,您可以为任何对象添加属性,包括 class 对象本身。给构造函数添加实例属性主要是一致性和可读性的问题。
添加到class定义中的数据属性在Python中称为class属性(我不知道Java,但我相信,这对应到静态变量)。这很有用,例如跟踪所有 class 个实例:
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)
正如您所指出的,这不是核心 python 语言的一部分。
这对我来说也是一个缺失的功能,原因如下:
readability/maintainability:使用 classic python 在构造函数或其他地方动态定义属性的方式,阅读代码时,对象的“契约”(或至少预期的鸭子契约)是什么并不明显。
compacity:仅使用
self.<foo> = <foo>
创建长构造函数并不是最有趣的,您需要的字段越多,您需要的行数就越多写扩展字段契约的能力,例如在默认值可变的情况下添加默认值工厂,或添加值验证器
创建混入的能力 classes,即 classes 依赖于某些字段实现某些功能,但是不强制使用任何构造函数。
这就是我创建 pyfields
的原因。使用此库,每个字段都定义为 class 成员:
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 constructor or using @autofields
to generate the field()
calls for you. pyfields
also provides an @autoclass
so you can even generate other class behaviours easily such as string representation, equality, conversion to dict, etc. See autoclass doc 进一步简化此示例。
请注意,pyfields
受到 attrs
等巨头的启发,但与众不同之处在于它保留了隔离原则。所以它不会 fiddle 背后有 __init__
或 __setattr__
。因此,这允许您的字段在集合上进行验证(而不仅仅是在构造函数中),并且还可以开发优雅的 mix-in classes 定义字段和方法,但不构造函数。