我是Python的新手(我来自Java),对类字段有以下疑问。
考虑这样的代码:
class Toy():
def__init__(self, color, age):
self.color = color
self.age = age
action_figure = Toy('red', 10)
好的,这样做很清楚而且很简单:
它正在定义玩具类。在构造函数中,方法是定义两个字段以及如何设置字段值。最后(在“主”中)将创建一个新的 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语言环境中,我将类字段声明为构造函数方法之外的变量。在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;
}
}
我还有 field3 , field4 和 field5 字段,这些字段不会由我的构造方法初始化(以防我可以设置使用setter方法第二次输入值。
我可以在Python中做类似的事情吗?我可以在构造方法之外声明类字段吗?
答案 0 :(得分:3)
python类与c ++ / java类的根本不同之处在于,c ++ / java类具有固定的数据结构和大小(字节),因为每个属性都在所有方法(通常作为私有变量)之外声明或定义。 python一切都是动态的(动态类型)。
实例变量(任何带有self.
的变量)只能在方法中定义。在构造函数与其他方法中选择定义属性的选择是关于其他人能够快速理解您的代码/数据结构(尽管由于动态性调用python类的数据结构是不合适的)
以动态为例,您甚至可以在运行时向实例添加新的方法和属性:
class A:
pass
a=A()
a.attr='something'
def f(self):
return 0
a.fun=f
a.fun()
# 0
答案 1 :(得分:2)
在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'
>>>
答案 2 :(得分:2)
在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
答案 3 :(得分:1)
由于Python是动态类型的,因此您无需事先声明变量,但是它们是在运行时初始化的。这也意味着您不必将实例属性添加到构造函数中,但是您可以在以后随时添加它们。实际上,您可以将属性添加到任何对象,包括类对象本身。向构造函数添加实例属性主要是一致性和可读性的问题。
添加到类定义中的数据属性在Python中称为类属性(我不知道Java,但我相信,这对应于静态变量)。这很有用,例如跟踪所有类实例:
Traceback (most recent call last):
File "/tmp/zeppelin_pyspark-9015139547840077087.py", line 331, in <module>
exec(code)
File "<stdin>", line 9, in <module>
File "/usr/lib/a-4.2.0-py-3.5.3/lib/python3.5/site-packages/databricks/koalas/__init__.py", line 41, in <module>
import pyarrow
File "/usr/lib/a-4.2.0-py-3.5.3/lib/python3.5/site-packages/pyarrow/__init__.py", line 45, in <module>
import pyarrow.compat as compat
AttributeError: module 'pyarrow' has no attribute 'compat'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/tmp/zeppelin_pyspark-9015139547840077087.py", line 333, in <module>
raise Exception(traceback.format_exc())
Exception: Traceback (most recent call last):
File "/tmp/zeppelin_pyspark-9015139547840077087.py", line 331, in <module>
exec(code)
File "<stdin>", line 9, in <module>
File "/usr/lib/a-4.2.0-py-3.5.3/lib/python3.5/site-packages/databricks/koalas/__init__.py", line 41, in <module>
import pyarrow
File "/usr/lib/a-4.2.0-py-3.5.3/lib/python3.5/site-packages/pyarrow/__init__.py", line 45, in <module>
import pyarrow.compat as compat
AttributeError: module 'pyarrow' has no attribute 'compat'
答案 4 :(得分:-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 constructor或using @autofields
to generate the field()
calls for you来简化此示例。 pyfields
支持@autoclass
,因此您甚至可以轻松地生成其他类行为,例如字符串表示,等式,转换为dict等。请参见autoclass doc。
请注意,pyfields
受到attrs
等巨人的启发,但与众不同之处在于它保留了隔离原则。因此,它不会在您的背后摆弄__init__
或__setattr__
。因此,这使您的字段可以在集合上(不仅在构造函数中)进行验证,还可以开发优雅的混合类来定义字段和方法,而无需构造函数。