Python 类应该在初始化时转换参数类型吗?如果是,怎么做?
我定义了一个包含5个实例变量的类Python 类应该在初始化时转换参数类型吗?如果是,怎么做?,python,python-3.5,Python,Python 3.5,我定义了一个包含5个实例变量的类 class PassPredictData: def __init__(self, rating, name, lat, long, elev): self.rating = rating # rest of init code 我要确保: 评级是一个整数 name是一个str lat,long,elev是浮动 当读取我的输入文件时,一切都可以根据我的类创建对象列表。当我开始比较值时,我得到了奇怪的结果,因为实例变量
class PassPredictData:
def __init__(self, rating, name, lat, long, elev):
self.rating = rating
# rest of init code
我要确保:
是一个整数评级
是一个strname
,lat
,long
是浮动elev
调用构造函数时,使用
int(string)
和float(string)
创建对象时,是“最具python风格的方法”来转换值吗?还是应该使用类内的逻辑来完成此转换?我个人会在将值传递给构造函数之前进行任何字符串解析,除非解析是其中之一(或)明确声明类的责任。我宁愿我的程序失败,因为我没有显式地强制转换值,而不是太灵活,最终陷入类似于0==“0”
的Javascript情况。也就是说,如果你想接受字符串作为参数,你可以调用int(my_参数)
或float(my_参数)
根据构造函数中的需要,这将确保无论您传递的是数字、字符串还是布尔值,这都是数字
如果您想更多地了解Python中的类型安全性,您可以查看类型检查器(如)和类属性中的类型安全性。定义自定义字段类型
一种方法是定义您自己的字段类型,并在其中进行转换和错误处理。这些字段将基于。这是您将在中找到的内容,等等
拥有这样的自定义字段将允许您强制转换、验证它们,这不仅适用于\uuuu init\uuuu
,而且适用于我们尝试为其赋值的任何地方
class Field:
type = None
def __init__(self, default=None):
self.value = default
def __get__(self, instance, cls):
if instance is None:
return self
return self.value
def __set__(self, instance, value):
# Here we could either try to cast the value to
# desired type or validate it and throw an error
# depending on the requirement.
try:
self.value = self.type(value)
except Exception:
raise ValueError('Failed to cast {value!r} to {type}'.format(
value=value, type=self.type
))
class IntField(Field):
type = int
class FloatField(Field):
type = float
class StrField(Field):
type = str
class PassPredictData:
rating = IntField()
name = StrField()
lat = FloatField()
long = FloatField()
elev = FloatField()
def __init__(self, rating, name, lat, long, elev):
self.rating = rating
self.name = name
self.lat = lat
self.long = long
self.elev = elev
演示:
>>> p = PassPredictData(1.2, 'foo', 1.1, 1.2, 1.3)
>>> p.lat = '123'
>>> p.lat
123.0
>>> p.lat = 'foo'
...
ValueError: Failed to cast 'foo' to <class 'float'>
>>> p.name = 123
>>> p.name
'123'
当我们在上面运行Mypy时,我们得到:
/so.py:15: error: Argument 2 to "PassPredictData" has incompatible type "int"; expected "str"
/so.py:17: error: Argument 1 to "PassPredictData" has incompatible type "float"; expected "int"
如果在Python解释器中键入
import this
,您将获得“Tim Peters的Python禅”。前三行似乎适用于您的情况:
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
我建议您这样实现您的类:
class PassPredictData:
def __init__(self, rating, name, lat, long, elev):
self.rating = int(rating)
self.name = str(name)
self.lat = float(lat)
self.long = float(long)
self.elev = float(elev)
这就是你在问题中提到的实现。它是简单的和明确的。情人眼里出西施
评论回复
该实现对类的编写者来说是显式的,而其他一些解决方案将类型转换隐藏在不透明机制后面
有一个有效的论据表明,从函数签名看,预期的参数类型并不明显。但是,这个问题意味着所有参数都是作为字符串传递的。在这种情况下,所有构造函数参数的预期类型都是str
。也许问题标题没有清楚地描述问题。可能更好的标题应该是“将字符串作为参数传递给构造函数时强制执行实例变量类型”
注意
我鼓励大家查看问题的开头,了解整个过程。编辑:(编辑,因为问题的主题已更改)我不建议在初始时间转换参数类型。例如:
class PassPredictData:
def __init__(self, rating, name, lat, long, elev):
self.rating = int(rating)
self.name = str(name)
...
在我看来,这种类型的隐式转换是危险的,原因很少
from typedecorator import params, returns, setup_typecheck, void, typed
class PassPredictData:
@void
@params(self=object, rating = int, name = str, lat = float, long = float, elev = float)
def __init__(self, rating, name, lat, long, elev):
self.rating = rating
self.name = name
self.lat = lat
self.long = long
self.elev = elev
setup_typecheck()
x = PassPredictData(1, "derp" , 6.8 , 9.8, 7.6) #works fine
x1 = PassPredictData(1.8, "derp" , 6.8 , 9.8, 7.6) #TypeError: argument rating = 1.8 doesn't match signature int
x2 = PassPredictData(1, "derp" , "gagaga" , 9.8, 7.6) #TypeError: argument lat = 'gagaga' doesn't match signature float
x3 = PassPredictData(1, 5 , 6.8 , 9.8, 7.6) #TypeError: argument name = 5 doesn't match signature str
对于Python3,可以使用注释语法:
抛出一个错误:
TypeError:参数名称=5与签名str不匹配
似乎有无数种方法可以做到这一点,但我使用的公式如下:
class PassPredictData(object):
types = {'lat' : float,
'long' : float,
'elev' : float,
'rating': int,
'name' : str,
}
def __init__(self, rating, name, lat, long, elev):
self.rating = rating
[rest of init code]
@classmethod
def from_string(cls, string):
[code to parse your string into a dict]
typed = {k: cls.types[k](v) for k, v in parsed.items()}
return cls(**typed)
这有一个好处:您可以直接使用re.groupdict()
生成dict(例如):
parsed=re.search('(?p\w):纬度:(?p\d+),经度:(?p\d+),海拔:(?p\d+)米(?p\d'),一些字符串)。在Python 3.5+中,可以使用类型提示和模块
类密码预测数据:
定义初始值(自,额定值:int,名称:str,lat:float,long:float,elev:float):
自我评价=评价
#初始化代码的其余部分
请注意,这些只是提示。Python实际上不会对它们做任何事情,比如在使用错误类型时显示错误。即使不依赖外部库,您也可以在几行代码中定义自己的简单类型检查装饰器。这使用Core Python中的inspect
模块来获取参数名,但如果没有它,您可以只使用带有类型列表的zip
args
,尽管这会使使用kwargs
变得困难
import inspect
def typecheck(**types):
def __f(f):
def _f(*args, **kwargs):
all_args = {n: a for a, n in zip(args, inspect.getargspec(f).args)}
all_args.update(kwargs)
for n, a in all_args.items():
t = types.get(n)
if t is not None and not isinstance(a, t):
print("WARNING: Expected {} for {}, got {}".format(t, n, a))
return f(*args, **kwargs)
return _f
return __f
class PassPredictData:
@typecheck(rating=int, name=str, elev=float)
def __init__(self, rating, name, lat=0.0, long=0.0, elev=0.0):
self.rating = rating
p = PassPredictData(5.1, "foo", elev=4)
# WARNING: Expected <class 'int'> for rating, got 5.1
# WARNING: Expected <class 'float'> for elev, got 4
或更简单的版本,不带检查
,但不适用于kwargs
,要求为每个参数提供类型,包括自身
(或无
无类型转换):
最简单的方法是按照您的建议,通过显式转换值。这是一个简单易读的解决方案。它还向用户说明您期望的值(int/float)。Python没有基元类型。一切都是一个对象,这是一个伟大的观点@juanpa.arrivillaga。这使得flavio milan的答案更加相关。记住Pytho
class PassPredictData(object):
types = {'lat' : float,
'long' : float,
'elev' : float,
'rating': int,
'name' : str,
}
def __init__(self, rating, name, lat, long, elev):
self.rating = rating
[rest of init code]
@classmethod
def from_string(cls, string):
[code to parse your string into a dict]
typed = {k: cls.types[k](v) for k, v in parsed.items()}
return cls(**typed)
import inspect
def typecheck(**types):
def __f(f):
def _f(*args, **kwargs):
all_args = {n: a for a, n in zip(args, inspect.getargspec(f).args)}
all_args.update(kwargs)
for n, a in all_args.items():
t = types.get(n)
if t is not None and not isinstance(a, t):
print("WARNING: Expected {} for {}, got {}".format(t, n, a))
return f(*args, **kwargs)
return _f
return __f
class PassPredictData:
@typecheck(rating=int, name=str, elev=float)
def __init__(self, rating, name, lat=0.0, long=0.0, elev=0.0):
self.rating = rating
p = PassPredictData(5.1, "foo", elev=4)
# WARNING: Expected <class 'int'> for rating, got 5.1
# WARNING: Expected <class 'float'> for elev, got 4
def typecast(**types):
def __f(f):
def _f(*args, **kwargs):
all_args = {n: a for a, n in zip(args, inspect.getargspec(f).args)}
all_args.update(kwargs)
for n, a in all_args.items():
t = types.get(n)
if t is not None:
all_args[n] = t(a) # instead of checking, just cast
return f(**all_args) # pass the map with the typecast params
return _f
return __f
class PassPredictData:
@typecast(rating=int, name=str, elev=float)
def __init__(self, rating, name, lat=0.0, long=0.0, elev=0.0):
print([rating, name, lat, long, elev])
p = PassPredictData("5", "foo", elev="3.14")
# Output of print: [5, 'foo', 0.0, 0.0, 3.14]
def typecast(*types):
def __f(f):
def _f(*args):
return f(*[t(a) if t is not None else a
for a, t in zip(args, types)])
return _f
return __f
class PassPredictData:
@typecast(None, int, str, float, float, float)
def __init__(self, rating, name, lat=0.0, long=0.0, elev=0.0):
print([rating, name, lat, long, elev])