-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Coding Style Conventions
- We follow the coding style defined by PEP 8.
- As an exception, we extend the maximum line length to 99 characters.
- We have some additional, project-specific conventions.
- Please refer to the "Optuna Specific Conventions" section for the details.
- We use type hints that are compatibility with Python 3.5:
# This is a good example.
# bad example
# Bad exampleFor simplicity.
def objective(trial):
...def objective(trial: optuna.trial.Trial) -> float:
...class PublicClass: # This class is exposed to external libraries.
def __init__(self):
self.public_field = 10 # This field is supposed to be accessed from other libraries.
self._package_private_field = 20 # This field is supposed to be accessed only within the same library.
self._private_field = 30 # This field is supposed to be accessed only within the same class.
def public_method(self):
pass
def _package_private_method(self):
pass
def _private_method(self):
pass
class _PackagePrivateClass: # This class is supposed to be accessed only within the same library.
def __init__(self):
self.package_private_field = 10 # This field can be accessed from any place within the same library.
self._private_field = 20 # This field is supposed to be accessed only within the same class.
def package_private_method(self):
pass
def _private_method(self):
pass
class _PrivateClass: # This class is supposed to be accessed only within the same module.
def __init__(self):
self.package_private_field = 10 # This field can be accessed from any place within the same module.
self._private_field = 20 # This field is supposed to be accessed only within the same class.
def package_private_method(self):
pass
def _private_method(self):
pass
def _package_private_function(): # This function is supposed to be accessed only within the same library.
pass
def _private_function(): # This function is supposed to be accessed only within the same module.
passPrefer pytest unittest with standard assertions over unittest tests.
def test_foo():
...
assert actual == expecteddef TestFoo(unittest.Testcase):
def test_foo(self):
...
self.assertEqual(actual, expected)Similarly, prefer pytest.raises for testing expected errors.
We follow Example Google Style Python Docstrings with a couple of exceptions:
- Add
Example:sections. -
ArgsandAttributessections always start with a new line. - No inline docstrings.
- The
__init__method must be documented in the class level docstring, not as a docstring on the__init__method. - Use sphinx-style links to Python objects.
def example_function(param1: int, param2: str) -> bool:
"""An example of function docstrings.
Example:
Using `testsetup` and `testcode`.
.. testsetup::
import numpy as np
.. testcode::
x = np.zeros(10)
Args:
param1:
The first parameter.
param2:
The second parameter.
Returns:
The return value. :obj:`True` for success, :obj:`False` otherwise.
"""
return Trueclass ExampleClass(object):
"""The summary line for a class docstring should fit on one line.
If the class has public attributes, they may be documented here
in an ``Attributes`` section and follow the same formatting as a
function's ``Args`` section.
Properties created with the ``@property`` decorator should be documented
in the property's getter method.
The `__init__` method must be documented in the class level docstring,
not as a docstring on the `__init__` method.
Args:
param1:
Description of `param1`.
param2:
Description of `param2`. Multiple
lines are supported.
Attributes:
attr1:
Description of `attr1`.
attr2:
Description of `attr2`.
"""
def __init__(self, param1: str, param2: Optional[int] = 0):
self.attr1 = param1
self.attr2 = param2
@property
def readonly_property(self) -> str:
"""Properties should be documented in their getter method."""
return "readonly_property"
@property
def readwrite_property(self) -> List[str]:
"""Properties with both a getter and setter
should only be documented in their getter method.
If the setter method contains notable behavior, it should be
mentioned here.
"""
return ["readwrite_property"]
@readwrite_property.setter
def readwrite_property(self, value: int) -> int:
value
def example_method(self, param1: str, param2: int) -> bool:
"""Class methods are similar to regular functions.
Example:
Using `testsetup` and `testcode`.
.. testsetup::
import numpy as np
.. testcode::
x = np.zeros(10)
Note:
Do not include the `self` parameter in the ``Args`` section.
(Instead of ``Note:``, you can use ``.. note::``.)
Args:
param1:
The first parameter.
param2:
The second parameter.
Returns:
:obj:`True` if successful, :obj:`False` otherwise.
"""
return TrueSome practices in Optuna are listed below.
This section is optional and should be used carefully. It should be documented if any of the followings:
- An error is non-obvious. Documentation helps users to understand what happens.
- An exception is expected to be caught in usercode. Documentation helps users to know what exception they should catch.
- When we indicate specifications. We may document exceptions typically in base classes.
def some_function(x: float):
"""...
...
Args:
x:
Positive real number.
"""def some_function(x: float):
"""...
...
Args:
x:
Positive real number.
Raises:
:exc:`ValueError`:
``x`` is negative.
"""If you implemented an experimental feature (class, method or function), please specify @experimental decorator to it.
Features that we consider as "experimental" include:
- Features that can be implemented in various designs & We don't know which design is the best or better.
For example,HyperbandPrunerwas first implemented in pr#809 but got improved with the changes in a handful of pull requests such as pr#1171 and pr#1188. - Algorithms that haven't been benchmarked extensively thus may or may not be stable for some objective functions, i.e., basically works well.
Features that we don't consider as "experimental" include:
- Features whose interface could change in the future due to the change of their dependencies.
More specifically, integration modules such asPyTorchLightningPruningCallbackare not "experimental" in Optuna.
from optuna._experimental import experimental
# Function: https://github.com/optuna/optuna/blob/248892e2/optuna/integration/lightgbm_tuner/__init__.py
@experimental("0.18.0")
def train(*args: Any, **kwargs: Any) -> Any:
...
# Class: https://github.com/optuna/optuna/blob/8d9576d7/optuna/pruners/hyperband.py
@experimental("1.1.0")
class HyperbandPruner(BasePruner):
...
# Method: https://github.com/optuna/optuna/blob/6e31e242/optuna/progress_bar.py
class _ProgressBar(object):
...
@experimental("1.2.0", name="Progress bar")
def _init_valid(self) -> None:
...Basically, we follow the Python official document. This section describes what needs special attention.
When we issue a warning regarding a particular runtime event, we use the following rule.
- If the issue is avoidable and the client application should be modified to eliminate the warning, please use
warnings.warn(). - If there is nothing the client application can do about the situation, but the event should still be noted, please use
optuna.logging.Logger.warnings()
import warnings
from optuna import logging
# Deprecate a feature which we cannot use the deprecation decorator, then use `warnings.warn()`
# https://github.com/optuna/optuna/blob/4d6e1c7e5f163744136dd1b67593d03cb977bc0f/optuna/cli.py
class _StudyOptimize(_BaseCommand):
...
def take_action(self, parsed_args):
# type: (Namespace) -> int
message = (
"The use of the `study optimize` command is deprecated. Please execute your Python "
"script directly instead."
)
warnings.warn(message, DeprecationWarning)
...
# Warn the exceptional sampler is used for the `CmaEsSampler` due to out of bounds, then use `optuna.logging.Logger.warnings()`.
# https://github.com/optuna/optuna/blob/4d6e1c7e5f163744136dd1b67593d03cb977bc0f/optuna/samplers/_cmaes.py
_logger = logging.get_logger(__name__)
class CmaEsSampler(BaseSampler):
...
def _log_independent_sampling(self, trial: FrozenTrial, param_name: str) -> None:
self._logger.warning(
"The parameter '{}' in trial#{} is sampled independently "
"by using `{}` instead of `CmaEsSampler` "
"(optimization performance may be degraded). "
"You can suppress this warning by setting `warn_independent_sampling` "
"to `False` in the constructor of `CmaEsSampler`, "
"if this independent sampling is intended behavior.".format(
param_name, trial.number, self._independent_sampler.__class__.__name__
)
)
...In docstrings and test cases, we prefer suggest_float to suggest_uniform, suggest_loguniform and suggest_discrete_uniform. See pr#2344.
def objective(trial: Trial) -> float:
x = trial.suggest_float("x", 0, 1)
y = trial.suggest_float("y", 1e-7, 1e-2, log=True)
z = trial.suggest_float("z", 0, 10, step=0.5)
...def objective(trial: Trial) -> float:
x = trial.suggest_uniform("x", 0, 1)
y = trial.suggest_loguniform("y", 1e-7, 1e-2)
z = trial.suggest_discrete_uniform("z", 0, 10, q=0.5)
...