英文:
argparse validation in a python class
问题
我尝试使用面向对象的方法来处理我的Python代码,最终将其转换为由PyInstaller创建的.EXE文件。这个想法是从用户输入中传递一系列参数给程序,最终会变成类似于(myprogram.exe -secureFolder C:/Users -thisisacsvfile.csv -countyCode 01069 -utmZone 15
)的形式。
我可以初始化一个类定义并传递参数,如下所示:
import argparse
import sys
class myprogram():
def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
self.secureFolder = secureFolder
self.inputCsvFile = inputCsvFile
self.countyCode = countyCode
self.utmZone = utmZone
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("secureFolder", help="A directory where the files are located", type=str)
parser.add_argument("inputCsvFile", help="A CSV file containing the results for a particular county (e.g. 36107.csv)", type=str)
parser.add_argument("countyCode", help="The FIPS county code", type=str)
parser.add_argument("utmZone", help="The UTM zone code for that specific county (e.g. 18)", type=int)
然而,我需要验证每个用户参数,这是我感到困惑的部分。换句话说,我需要检查secureFolder
是否存在,inputCsvFile
是否确实是CSV文件并且包含特定列以及其他操作来验证其余的参数。我不确定这些操作应该在哪里执行?在类定义之后还是之前?
在采用面向对象的方法之前,我是这样做的:
# 检查是否传递了所有参数
undefined_arguments = [attr for attr in vars(args) if getattr(args, attr) is None]
if undefined_arguments:
print("以下参数未定义:", undefined_arguments)
else:
print("所有参数都已定义。")
# 1a. 检查inputCsvFile
if args.inputCsvFile is None:
sys.exit("请选择要处理的输入CSV文件 (例如:inputCsvFile.../myfile.csv)")
else:
if not os.path.isfile(args.inputCsvFile):
sys.exit(f"文件 {args.inputCsvFile} 似乎不存在...请检查文件是否存在或您是否具有访问权限")
else:
grid_file_csv = args.inputCsvFile
print(f"{args.inputCsvFile} 已找到...")
# 1b. 检查inputCsvFile是否为CSV文件
if not args.inputCsvFile.endswith('.csv'):
raise ValueError("无效的输入文件。预期是一个CSV文件。")
sys.exit('未传递正确的CSV文件...')
希望这可以帮助你理解在面向对象编程方法中应该如何验证参数。如果需要进一步的帮助,请随时提问。
英文:
I'm trying an OOP approach to my Python code which eventually will be converted to an .EXE file created with PyInstaller. The idea is to pass a series of arguments from the user input (n to a program that eventually will go something like (myprogram.exe -secureFolder C:/Users -thisisacsvfile.csv -countyCode 01069 -utmZone 15
).
I can initialize a class definition and pass the arguments like:
import argparse
import sys
class myprogram():
def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
self.secureFolder = secureFolder
self.inputCsvFile = inputCsvFile
self.countyCode = countyCode
self.utmZone = utmZone
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("secureFolder", help = "A directory where the files are located", type=str)
parser.add_argument("inputCsvFile", help="A CSV file containing the results for a particular county (e.g. 36107.csv)", type=str)
parser.add_argument("countyCode", help = "The FIPS county code", type = str)
parser.add_argument("utmZone", help = "The UTM zone code for that specific county (e.g. 18)", type = int)
However, I need to validate every user argument and that's the part where I'm getting confused. In other words, I need to check if the secureFolder
exists, if the inputCsvFile
is indeed a CSV and contains some specific columns and other operations for the rest of the arguments. What I don't know exactly, where do I perform these operations? After the class definition?
Before the OOP approach, I was doing something like:
# Check if all the arguments were passed
undefined_arguments = [attr for attr in vars(args) if getattr(args, attr) is None]
if undefined_arguments:
print("The following arguments were not defined:", undefined_arguments)
else:
print("All arguments were defined.")
# 1a. Check inputCsvFile
if args.inputCsvFile is None:
sys.exit("Please select an input CSV file to process (e.g. inputCsvFile.../myfile.csv) ")
else:
if not os.path.isfile(args.inputCsvFile):
sys.exit (f"File {args.inputCsvFile} doesn't appear to exists...please check if the file exists or if you have privileges to access it")
else:
grid_file_csv = args.inputCsvFile
print (f"{args.inputCsvFile} found...")
# 1b. Check if inputCsvFile is a CSV:
if not args.inputCsvFile.endswith('.csv'):
raise ValueError("Invalid input file. Expected a CSV file.")
sys.exit('No propper CSV file has been passed...')
# 2. Check if the FIPS code
if args.countyCode is None:
sys.exit("Please specify a valid county code (e.g. -countyCode3607)")
# Check the UTM area code
if args.utmzone is None:
sys.exit("Please specify a valid UTM zone area (e.g. -utmZone 16): ")
if args.utmZone is not None:
val = args.utmZone
if val < 1 and val > 20:
raise Exception('UTM zone area should be between 1 and 20')
sys.exit()
答案1
得分: 1
这主要是一个风格和偏好的问题。在我看来,尽可能在构建任何类之前都应该验证构建该类所需的值 - 尤其是当它依赖于用户输入时。
所以,获取命令行参数,验证它们,然后构建你的类。类似于这样:
import argparse
from sys import stderr
import os
class myprogram():
def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
self.secureFolder = secureFolder
self.inputCsvFile = inputCsvFile
self.countyCode = countyCode
self.utmZone = utmZone
def __str__(self):
return f'{self.secureFolder=}, {self.inputCsvFile=}, {self.countyCode=}, {self.utmZone=}'
@staticmethod
def validate(ns):
if not os.path.isdir(ns.secureFolder):
print(f'{ns.secureFolder} is not a valid folder', file=stderr)
return None
try:
with open(ns.inputCsvFile) as _:
...
except Exception as e:
print(f'Unable to open {ns.inputCsvFile} due to {e}', file=stderr)
return None
if ns.countyCode is None:
print(f'{ns.countyCode} is an invalid county code', file=stderr)
return None
if (z := ns.utmZone) is None or z < 1 or z > 60:
print(f'{z} is not a valid utmZone', file=stderr)
return None
return myprogram(ns.secureFolder, ns.inputCsvFile, ns.countyCode, ns.utmZone)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
cli = [
('-secureFolder', 'A directory where the files are located', str),
('-inputCsvFile', 'A CSV file containing the results for a particular county (e.g., 36107.csv)', str),
('-countyCode', 'The FIPS county code', str),
('-utmZone', 'The UTM zone code for that specific county (e.g., 18)', int)
]
for c, h, t in cli:
parser.add_argument(c, help=h, type=t)
args = parser.parse_args()
if (mp := myprogram.validate(args)) is None:
print('Unable to construct class instance')
else:
# 此时我们有一个有效的myprogram类实例(mp)
print(mp)
希望这对你有所帮助。
英文:
This is largely a question of style and preference. In my view, wherever possible, values needed to construct any class should be validated before the class is constructed - especially when it relies on user input.
So, get the command line arguments, validate them, then construct your class. Something like this:
import argparse
from sys import stderr
import os
class myprogram():
def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
self.secureFolder = secureFolder
self.inputCsvFile = inputCsvFile
self.countyCode = countyCode
self.utmZone = utmZone
def __str__(self):
return f'{self.secureFolder=}, {self.inputCsvFile=}, {self.countyCode=}, {self.utmZone=}'
@staticmethod
def validate(ns):
if not os.path.isdir(ns.secureFolder):
print(f'{ns.secureFolder} is not a valid folder', file=stderr)
return None
try:
with open(ns.inputCsvFile) as _:
...
except Exception as e:
print(f'Unable to open {ns.inputCsvFile} due to {e}', file=stderr)
return None
if ns.countyCode is None:
print(f'{ns.countyCode} is an invalid county code', file=stderr)
return None
if (z := ns.utmZone) is None or z < 1 or z > 60:
print(f'{z} is not a valid utmZone', file=stderr)
return None
return myprogram(ns.secureFolder, ns.inputCsvFile, ns.countyCode, ns.utmZone)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
cli = [
('-secureFolder', 'A directory where the files are located', str),
('-inputCsvFile', 'A CSV file containing the results for a particular county (e.g., 36107.csv)', str),
('-countyCode', 'The FIPS county code', str),
('-utmZone', 'The UTM zone code for that specific county (e.g., 18)', int)
]
for c, h, t in cli:
parser.add_argument(c, help=h, type=t)
args = parser.parse_args()
if (mp := myprogram.validate(args)) is None:
print('Unable to construct class instance')
else:
# at this point we have a valid myprogram class instance (mp)
print(mp)
答案2
得分: 0
argparse
库可以为您执行一些验证操作,例如确保参数是必需的。对于其余部分,您可以编写小而简洁的验证函数:
import argparse
import pathlib
class MyProgram:
def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
self.secureFolder = secureFolder
self.inputCsvFile = inputCsvFile
self.countyCode = countyCode
self.utmZone = utmZone
def validate_dir(value):
path = pathlib.Path(value)
if not path.is_dir():
raise ValueError()
return value
def validate_csv(value):
path = pathlib.Path(value)
if not path.exists():
raise ValueError()
if path.suffix != ".csv":
raise ValueError()
return value
def validate_utm_zone(value):
value = int(value)
if not (1 <= value <= 20):
raise ValueError()
return value
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-secureFolder",
help="文件所在的目录",
type=validate_dir,
required=True,
)
parser.add_argument(
"-inputCsvFile",
help="包含特定县的结果的CSV文件",
type=validate_csv,
required=True,
)
parser.add_argument("-countyCode", help="FIPS县代码", required=True)
parser.add_argument(
"-utmZone",
help="特定县的UTM区域代码(例如,18)",
type=validate_utm_zone,
required=True,
)
options = parser.parse_args()
print(options)
my_program = MyProgram(
secureFolder=options.secureFolder,
inputCsvFile=options.inputCsvFile,
countyCode=options.countyCode,
utmZone=options.utmZone,
)
以下是一些示例运行:
# 正常情况
$ python3 main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 15
Namespace(secureFolder='./myfolder', inputCsvFile='input.csv', countyCode='code-blah', utmZone=15)
# 没有提供任何参数
$ python3 main.py
# 目录/foobar不存在
$ python3 main.py -secureFolder /foobar -inputCsvFile input.csv -countyCode code-blah -utmZone 15
# 无效的utmZone
$ python3 main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone foo
# utmZone不在范围内
$ python3 main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 21
# CSV文件不以.csv结尾
$ python3 main.py -secureFolder ./myfolder -inputCsvFile main.py -countyCode code-blah -utmZone 15
# csv文件不存在
$ python3 main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 15
# 获取帮助
$ python3 main.py -h
英文:
The argparse
library can do some validations for you, such as ensuring that the argument is required. For the rest, you can write small, concise validation functions:
import argparse
import pathlib
class MyProgram:
def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
self.secureFolder = secureFolder
self.inputCsvFile = inputCsvFile
self.countyCode = countyCode
self.utmZone = utmZone
def validate_dir(value):
path = pathlib.Path(value)
if not path.is_dir():
raise ValueError()
return value
def validate_csv(value):
path = pathlib.Path(value)
if not path.exists():
raise ValueError()
if path.suffix != ".csv":
raise ValueError()
return value
def validate_utm_zone(value):
value = int(value)
if not (1 <= value <= 20):
raise ValueError()
return value
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-secureFolder",
help="A directory where the files are located",
type=validate_dir,
required=True,
)
parser.add_argument(
"-inputCsvFile",
help="A CSV file containing the results for a particular county",
type=validate_csv,
required=True,
)
parser.add_argument("-countyCode", help="The FIPS county code", required=True)
parser.add_argument(
"-utmZone",
help="The UTM zone code for that specific county (e.g. 18)",
type=validate_utm_zone,
required=True,
)
options = parser.parse_args()
print(options)
my_program = MyProgram(
secureFolder=options.secureFolder,
inputCsvFile=options.inputCsvFile,
countyCode=options.countyCode,
utmZone=options.utmZone,
)
Here are some sample runs
# Happy path
$ python3 main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 15
Namespace(secureFolder='./myfolder', inputCsvFile='input.csv', countyCode='code-blah', utmZone=15)
# None of the arguments supplied
$ python3 main.py
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: the following arguments are required: -secureFolder, -inputCsvFile, -countyCode, -utmZone
# Directory /foobar does not exist
$ python3 main.py -secureFolder /foobar -inputCsvFile input.csv -countyCode code-blah -utmZone 15
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -secureFolder: invalid validate_dir value: '/foobar'
# Invalid utmZone
$ python3 main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone foo
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -utmZone: invalid validate_utm_zone value: 'foo'
# utmZone not in range
$ python3 main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 21
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -utmZone: invalid validate_utm_zone value: '21'
# CSV file does not end with .csv
$ python3 main.py -secureFolder ./myfolder -inputCsvFile main.py -countyCode code-blah -utmZone 15
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -inputCsvFile: invalid validate_csv value: 'main.py'
# csv does not exist
$ python3 main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 15
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -inputCsvFile: invalid validate_csv value: 'input.csv'
# Get help
$ python3 main.py -h
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
options:
-h, --help show this help message and exit
-secureFolder SECUREFOLDER
A directory where the files are located
-inputCsvFile INPUTCSVFILE
A CSV file containing the results for a particular county
-countyCode COUNTYCODE
The FIPS county code
-utmZone UTMZONE The UTM zone code for that specific county (e.g. 18)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论