Tôi đang viết một lớp học có một số phương pháp hoạt động trên các loại tương tự của đối số:cách Pythonic chuyển đổi các tham số để cùng một tiêu chuẩn trong tất cả các phương pháp của một lớp
class TheClass():
def func1(self, data, params, interval):
....
def func2(self, data, params):
....
def func3(self, data, interval):
....
def func4(self, params):
....
...
Có một quy ước nhất định về những đối số (ví dụ: data
/params
phải là numpy.farrays
, interval
- a list
của 2 floats
), nhưng tôi muốn cho phép người dùng có nhiều tự do hơn: ví dụ: chức năng nên chấp nhận int
như data
hoặc params
, hoặc chỉ có một int
như một interval
và sau đó cho rằng đây là điểm cuối với điểm xuất phát 0
vv
Vì vậy, để tránh tất cả những biến đổi trong phương pháp, mà nên làm chỉ logic, tôi đang sử dụng trang trí như thế này:
def convertparameters(*types):
def wrapper(func):
def new_func(self, *args, **kwargs):
# Check if we got enough parameters
if len(types) > len(args):
raise Exception('Not enough parameters')
# Convert parameters
new_args = list(args)
for ind, tip in enumerate(types):
if tip == "data":
new_args[ind] = _convert_data(new_args[ind])
elif tip == "params":
new_args[ind] = _convert_params(new_args[ind])
elif tip == "interval":
new_args[ind] = _convert_interval(new_args[ind])
else:
raise Exception('Unknown type for parameter')
return func(self, *new_args, **kwargs)
return new_func
return wrapper
đâu _convert_data
, _convert_params
và _convert_interval
làm công việc bẩn. Sau đó, tôi xác định các lớp như sau:
class TheClass():
@convertparameters("data", "params", "interval")
def func1(self, data, params, interval):
....
@convertparameters("data", "params")
def func2(self, data, params):
....
@convertparameters("data", "interval")
def func3(self, data, interval):
....
@convertparameters("params")
def func4(self, params):
....
...
Nó hiện các trick, nhưng cũng có khá nhiều điều khá đáng lo ngại:
- Nó clutters mã (mặc dù nó là một vấn đề nhỏ, và tôi tìm giải pháp này nhỏ gọn hơn nhiều so với lời gọi rõ ràng của hàm chuyển đổi ở đầu mỗi phương thức)
- Nếu tôi cần kết hợp trang trí này với một trang trí khác (
@staticmethod
hoặc thứ gì đó sau quá trình xử lý) những trang trí này quan trọng .210
- Điều đáng lo ngại nhất là các trang trí của mẫu này hoàn toàn ẩn cấu trúc tham số của phương pháp và IPython thấy nó như
func1(*args, **kwargs)
Có bất kỳ tốt hơn (hoặc nhiều "Pythonic") cách để làm biến đổi thông số khổng lồ như vậy?
UPDATE 1: GIẢI PHÁP dựa trên n9code 's đề nghị
Để tránh nhầm lẫn có một sửa đổi của wrapper convertparameters
rằng giải quyết vấn đề thứ 3 (mặt nạ ký và docstring của phương pháp) - được đề xuất bởi n9code cho Python> 2.5.
Sử dụng decorator
mô-đun (được cài đặt riêng: pip install decorator
) chúng ta có thể chuyển tất cả của chức năng "siêu dữ liệu" (docstring, tên và chữ ký) đồng thời loại bỏ cấu trúc lồng nhau bên trong wrapper
from decorator import decorator
def convertparameters(*types):
@decorator
def wrapper(func, self, *args, **kwargs):
# Check if we got enough parameters
if len(types) > len(args):
raise Exception('Not enough parameters')
# Convert parameters
new_args = list(args)
for ind, tip in enumerate(types):
if tip == "data":
new_args[ind] = _convert_data(new_args[ind])
elif tip == "params":
new_args[ind] = _convert_params(new_args[ind])
elif tip == "interval":
new_args[ind] = _convert_interval(new_args[ind])
else:
raise Exception('Unknown type for parameter')
return func(self, *new_args, **kwargs)
return wrapper
UPDATE 2: SỬA ĐỔI GIẢI PHÁP dựa trên zmbq 's đề nghị
Sử dụng mô-đun inspect
chúng tôi cũng có thể thoát khỏi các đối số của trang trí, kiểm tra tên của argu ments của hàm ban đầu.Điều này sẽ loại bỏ một lớp trang trí khác
from decorator import decorator
import inspect
@decorator
def wrapper(func, self, *args, **kwargs):
specs = inspect.getargspec(func)
# Convert parameters
new_args = list(args)
for ind, name in enumerate(specs.args[1:]):
if name == "data":
new_args[ind] = _convert_data(new_args[ind])
elif name == "params":
new_args[ind] = _convert_params(new_args[ind])
elif name == "interval":
new_args[ind] = _convert_interval(new_args[ind])
return func(self, *new_args, **kwargs)
Và việc sử dụng đơn giản hơn nhiều. Điều quan trọng duy nhất là tiếp tục sử dụng cùng tên cho các đối số giữa các hàm khác nhau.
class TheClass():
@convertparameters
def func1(self, data, params, interval):
....
@convertparameters
def func2(self, data, params):
....
Cảm ơn, tôi không biết về 'kết thúc tốt đẹp'. Tôi giả sử có nên cũng 'return new_func' và' return wrapper'? – Vladimir
Tôi có thêm một câu hỏi: 'kết thúc tốt đẹp' chuyển đúng chuỗi tài liệu. Tuy nhiên chữ ký vẫn là 'func1 (* args, ** kwargs)'. Có cách nào để chuyển chữ ký không? – Vladimir
Tôi đoán bạn đang sử dụng Python 2. Trong trường hợp của Python 3 chữ ký cũng sẽ được bảo tồn. Kiểm tra cập nhật của tôi. – bagrat