如何最好地实现对于此 class 的每个对象都不同的 class 方法
How to best implement a class method that is different for every object of this class
作为一名教师,我想为数学问题编写一个工作表生成器。
Python 应该生成数学问题及其解决方案(例如创建多项式函数并计算它们的零点)。然后它会写入一个 LaTeX 输入文件并通过 pdflatex
创建一个 pdf。它适用于几个问题,但现在我想概括它并使其面向对象以加速创建更多工作表。
所以我用几个参数和方法创建了一个 class Problem
。但是每个 Problem
-instance 都应该有不同的功能来创建数学问题及其解决方案的文本(因为每个对象都是不同的数学问题)。而且我不知道我应该如何以 elegant/proper 方式管理它。
目前我正在做这样的事情:
import random
class Problem_generator(object):
def __init__(self):
self.problemlist = []
def create_worksheet(self):
""" Creates the latex-document by writing the output in a LaTeX input file. For
this minimal example, I'll just print the outputs instead.
"""
for problem in self.problemlist:
text = problem.create()
print("Problem: " + text[0])
print("Solution: " + text[1])
class Problem(object):
def __init__(self, problem_generator, function):
# do some stuff like to create Tkinter-GUI-objects for every problem
self.function = function
problem_generator.problemlist.append(self)
def create(self):
return self.function()
def add_numbers():
""" Create the problem to add two numbers. """
a, b = random.randint(0, 100), random.randint(0, 100)
problem_text = str(a) + " + " + str(b)
solution_text = str(a+b)
return problem_text, solution_text
generator = Problem_generator()
problem_1 = Problem(generator, add_numbers)
generator.create_worksheet() # in the complete program I start this over a GUI
它工作正常,但感觉不“对”。
我还考虑过使用 create()
方法实现 Problem
class,该方法只会引发未实现的错误,然后为我创建的每个问题更新 create()
方法。但据我所知,这将为 class.
的每个对象更新 create()
方法
所以我很乐意得到一些 tips/suggestions 如何以“优雅”的方式处理所描述的问题。
我会这样做:
import random
from typing import Callable, List, Tuple
ProblemCreator = Callable[[], Tuple[str, str]] # returns (problem, solution)
class Problem:
def __init__(self, function: ProblemCreator) -> None:
# do some stuff like to create Tkinter-GUI-objects for every problem
self.create = function
class ProblemGenerator:
def __init__(self) -> None:
self.problem_list: List[Problem] = []
def create_worksheet(self) -> None:
"""
Creates the latex-document by writing the output in a *.tex-file.
For this minimal example, I'll just print the outputs instead.
"""
for problem in self.problem_list:
p, s = problem.create()
print(f"Problem: {p}")
print(f"Solution: {s}")
def generate_problem(self, problem: ProblemCreator) -> None:
self.problem_list.append(Problem(problem))
def add_numbers() -> Tuple[str, str]:
""" Create the problem to add two numbers. """
a, b = random.randint(0, 100), random.randint(0, 100)
return f"{a} + {b}", f"{a+b}"
generator = ProblemGenerator()
generator.generate_problem(add_numbers)
generator.create_worksheet() # in the complete program I start this over a GUI
我使用了类型注释和其他 Python 3 种功能(如 f-strings)来提高清晰度。
create
不需要成为一个方法——只要让它成为一个可调用的属性(我已经给这个可调用的类型起了一个名字,ProblemCreator
,因为它形成了一个此接口的重要部分)。同样,Problem
不需要知道 ProblemGenerator
并负责将自己添加到生成器列表;它只是创建了一个你不需要的循环依赖。相反,让 ProblemGenerator
负责产生问题(就像它的名字所说的那样)!
我真的没有发现 Problem_generator class 有任何用处,下面是我的做法(我添加了 ProblemGenerator class 但你可以循环调用 problem.create)
使用这个你可以
- 将问题定义为问题的子class(参见 AddTwoNumbers class)
- 将问题定义为函数(参见 subtract_numbers 函数)并使用 problem_decorator 将它们转换为函数问题(问题的子class)。
from abc import ABC, abstractmethod
from functools import wraps
import random
from typing import Callable, List, Tuple
random.seed(0) # for reproducability
class Problem(ABC):
@property
@abstractmethod
def text(self) -> str:
pass
@property
@abstractmethod
def solution_text(self) -> str:
pass
class AddTwoNumbers(Problem):
def __init__(self) -> None:
self._a = random.randint(0, 100)
self._b = random.randint(0, 100)
@property
def text(self) -> str:
return f'{self._a} + {self._b}'
@property
def solution_text(self) -> str:
return str(self._a + self._b)
# If you want to define functions as problems you can do something like that
class FunctionProblem(Problem):
def __init__(self, func: Callable[[], Tuple[str, str]]):
self._text, self._solution_text = func()
@property
def text(self) -> str:
return self._text
@property
def solution_text(self) -> str:
return self._solution_text
# Define a decorator so that functions become FunctionProblems
def problem_decorator(func: Callable[[], Tuple[str, str]]) -> Callable[[], FunctionProblem]:
@wraps(func)
def wrapper():
return FunctionProblem(func)
return wrapper
@problem_decorator
def subtract_numbers() -> Tuple[str, str]:
a, b = random.randint(0, 100), random.randint(0, 100)
text = f'{a} - {b}'
solution = str(a - b)
return text, solution
# If you really want to define a ProblemGenerator
class ProblemGenerator:
def __init__(self, *problems: Problem) -> None:
self.problems = list(problems)
def add_problem(self, problem: Problem) -> None:
self.problems.append(problem)
def create_worksheet(self) -> List[Tuple[str, str]]:
for problem in self.problems:
print(f'Problem text is {problem.text!r}, Solution is {problem.solution_text!r}')
generator = ProblemGenerator(AddTwoNumbers())
generator.add_problem(subtract_numbers())
generator.create_worksheet()
打印
Problem text is '49 + 97', Solution is '146'
Problem text is '53 - 5', Solution is '48'
正如我在评论中所说,classic object-oriented programming (OOP) 处理这种情况的方法是定义一个抽象基础 class 与整体接口,也许还有一些通用辅助方法,然后定义特定于问题的“具体”子classes。这是一个玩具示例,根据您问题中的代码在 Python 上执行类似操作。如果看起来您正在创建一堆几乎相同的 classes。有时补救办法是 subclass 你的 subclasses,但通常这只是意味着你没有很好地抽象问题......
从您的示例代码中的评论来看,您似乎还希望 classes 负责为图形用户界面 (GUI) 创建自己的 tkinter 对象。一般来说,这可能不是一个好主意 — classes 通常应该只有一个 single responsibility,这意味着尝试将它们中的许多堆放在上面会使测试、调试和扩展您拥有的东西变得非常复杂更难了。
有一种名为 model–view–controller (MVC) 的常用软件设计模式,通常用于开发用户界面,将一个程序分为三个相互关联的组件,但将每个组件分开以降低复杂性——所以我建议你投资一个研究它的时间。
import abc
import random
class Problem(metaclass=abc.ABCMeta):
""" Abstract base class of all mathematical problems. """
def __init__(self):
self.lines = []
@abc.abstractmethod
def create_problem(self):
""" Creates input to be fed to the create_worksheet method. """
...
def create_worksheet(self, indent=4, char=' '):
""" Creates the latex-document by writing the output in a LaTeX input file.
In this toy example, it just print the lines the create_problem() method
generated.
"""
padding = indent * char
for line in self.lines:
print(padding + line)
class AddNumbers(Problem):
def __init__(self):
super().__init__() # Initialize base class.
self.create_problem() # Create subclass-specific data.
def create_problem(self):
a, b = random.randint(0, 100), random.randint(0, 100)
self.lines.append(f'Problem: Add the two numbers {a} and {b} together')
self.lines.append(f'Solution: {a+b}')
if __name__ == '__main__':
# Create some sample Problem subclass instances.
problems = [AddNumbers(), AddNumbers()]
# Create worksheet the worksheet for each one.
for i, problem in enumerate(problems, start=1):
print(f'Worksheet {i}:')
problem.create_worksheet(indent=2)
作为一名教师,我想为数学问题编写一个工作表生成器。
Python 应该生成数学问题及其解决方案(例如创建多项式函数并计算它们的零点)。然后它会写入一个 LaTeX 输入文件并通过 pdflatex
创建一个 pdf。它适用于几个问题,但现在我想概括它并使其面向对象以加速创建更多工作表。
所以我用几个参数和方法创建了一个 class Problem
。但是每个 Problem
-instance 都应该有不同的功能来创建数学问题及其解决方案的文本(因为每个对象都是不同的数学问题)。而且我不知道我应该如何以 elegant/proper 方式管理它。
目前我正在做这样的事情:
import random
class Problem_generator(object):
def __init__(self):
self.problemlist = []
def create_worksheet(self):
""" Creates the latex-document by writing the output in a LaTeX input file. For
this minimal example, I'll just print the outputs instead.
"""
for problem in self.problemlist:
text = problem.create()
print("Problem: " + text[0])
print("Solution: " + text[1])
class Problem(object):
def __init__(self, problem_generator, function):
# do some stuff like to create Tkinter-GUI-objects for every problem
self.function = function
problem_generator.problemlist.append(self)
def create(self):
return self.function()
def add_numbers():
""" Create the problem to add two numbers. """
a, b = random.randint(0, 100), random.randint(0, 100)
problem_text = str(a) + " + " + str(b)
solution_text = str(a+b)
return problem_text, solution_text
generator = Problem_generator()
problem_1 = Problem(generator, add_numbers)
generator.create_worksheet() # in the complete program I start this over a GUI
它工作正常,但感觉不“对”。
我还考虑过使用 create()
方法实现 Problem
class,该方法只会引发未实现的错误,然后为我创建的每个问题更新 create()
方法。但据我所知,这将为 class.
create()
方法
所以我很乐意得到一些 tips/suggestions 如何以“优雅”的方式处理所描述的问题。
我会这样做:
import random
from typing import Callable, List, Tuple
ProblemCreator = Callable[[], Tuple[str, str]] # returns (problem, solution)
class Problem:
def __init__(self, function: ProblemCreator) -> None:
# do some stuff like to create Tkinter-GUI-objects for every problem
self.create = function
class ProblemGenerator:
def __init__(self) -> None:
self.problem_list: List[Problem] = []
def create_worksheet(self) -> None:
"""
Creates the latex-document by writing the output in a *.tex-file.
For this minimal example, I'll just print the outputs instead.
"""
for problem in self.problem_list:
p, s = problem.create()
print(f"Problem: {p}")
print(f"Solution: {s}")
def generate_problem(self, problem: ProblemCreator) -> None:
self.problem_list.append(Problem(problem))
def add_numbers() -> Tuple[str, str]:
""" Create the problem to add two numbers. """
a, b = random.randint(0, 100), random.randint(0, 100)
return f"{a} + {b}", f"{a+b}"
generator = ProblemGenerator()
generator.generate_problem(add_numbers)
generator.create_worksheet() # in the complete program I start this over a GUI
我使用了类型注释和其他 Python 3 种功能(如 f-strings)来提高清晰度。
create
不需要成为一个方法——只要让它成为一个可调用的属性(我已经给这个可调用的类型起了一个名字,ProblemCreator
,因为它形成了一个此接口的重要部分)。同样,Problem
不需要知道 ProblemGenerator
并负责将自己添加到生成器列表;它只是创建了一个你不需要的循环依赖。相反,让 ProblemGenerator
负责产生问题(就像它的名字所说的那样)!
我真的没有发现 Problem_generator class 有任何用处,下面是我的做法(我添加了 ProblemGenerator class 但你可以循环调用 problem.create)
使用这个你可以
- 将问题定义为问题的子class(参见 AddTwoNumbers class)
- 将问题定义为函数(参见 subtract_numbers 函数)并使用 problem_decorator 将它们转换为函数问题(问题的子class)。
from abc import ABC, abstractmethod
from functools import wraps
import random
from typing import Callable, List, Tuple
random.seed(0) # for reproducability
class Problem(ABC):
@property
@abstractmethod
def text(self) -> str:
pass
@property
@abstractmethod
def solution_text(self) -> str:
pass
class AddTwoNumbers(Problem):
def __init__(self) -> None:
self._a = random.randint(0, 100)
self._b = random.randint(0, 100)
@property
def text(self) -> str:
return f'{self._a} + {self._b}'
@property
def solution_text(self) -> str:
return str(self._a + self._b)
# If you want to define functions as problems you can do something like that
class FunctionProblem(Problem):
def __init__(self, func: Callable[[], Tuple[str, str]]):
self._text, self._solution_text = func()
@property
def text(self) -> str:
return self._text
@property
def solution_text(self) -> str:
return self._solution_text
# Define a decorator so that functions become FunctionProblems
def problem_decorator(func: Callable[[], Tuple[str, str]]) -> Callable[[], FunctionProblem]:
@wraps(func)
def wrapper():
return FunctionProblem(func)
return wrapper
@problem_decorator
def subtract_numbers() -> Tuple[str, str]:
a, b = random.randint(0, 100), random.randint(0, 100)
text = f'{a} - {b}'
solution = str(a - b)
return text, solution
# If you really want to define a ProblemGenerator
class ProblemGenerator:
def __init__(self, *problems: Problem) -> None:
self.problems = list(problems)
def add_problem(self, problem: Problem) -> None:
self.problems.append(problem)
def create_worksheet(self) -> List[Tuple[str, str]]:
for problem in self.problems:
print(f'Problem text is {problem.text!r}, Solution is {problem.solution_text!r}')
generator = ProblemGenerator(AddTwoNumbers())
generator.add_problem(subtract_numbers())
generator.create_worksheet()
打印
Problem text is '49 + 97', Solution is '146'
Problem text is '53 - 5', Solution is '48'
正如我在评论中所说,classic object-oriented programming (OOP) 处理这种情况的方法是定义一个抽象基础 class 与整体接口,也许还有一些通用辅助方法,然后定义特定于问题的“具体”子classes。这是一个玩具示例,根据您问题中的代码在 Python 上执行类似操作。如果看起来您正在创建一堆几乎相同的 classes。有时补救办法是 subclass 你的 subclasses,但通常这只是意味着你没有很好地抽象问题......
从您的示例代码中的评论来看,您似乎还希望 classes 负责为图形用户界面 (GUI) 创建自己的 tkinter 对象。一般来说,这可能不是一个好主意 — classes 通常应该只有一个 single responsibility,这意味着尝试将它们中的许多堆放在上面会使测试、调试和扩展您拥有的东西变得非常复杂更难了。
有一种名为 model–view–controller (MVC) 的常用软件设计模式,通常用于开发用户界面,将一个程序分为三个相互关联的组件,但将每个组件分开以降低复杂性——所以我建议你投资一个研究它的时间。
import abc
import random
class Problem(metaclass=abc.ABCMeta):
""" Abstract base class of all mathematical problems. """
def __init__(self):
self.lines = []
@abc.abstractmethod
def create_problem(self):
""" Creates input to be fed to the create_worksheet method. """
...
def create_worksheet(self, indent=4, char=' '):
""" Creates the latex-document by writing the output in a LaTeX input file.
In this toy example, it just print the lines the create_problem() method
generated.
"""
padding = indent * char
for line in self.lines:
print(padding + line)
class AddNumbers(Problem):
def __init__(self):
super().__init__() # Initialize base class.
self.create_problem() # Create subclass-specific data.
def create_problem(self):
a, b = random.randint(0, 100), random.randint(0, 100)
self.lines.append(f'Problem: Add the two numbers {a} and {b} together')
self.lines.append(f'Solution: {a+b}')
if __name__ == '__main__':
# Create some sample Problem subclass instances.
problems = [AddNumbers(), AddNumbers()]
# Create worksheet the worksheet for each one.
for i, problem in enumerate(problems, start=1):
print(f'Worksheet {i}:')
problem.create_worksheet(indent=2)