yapf formats long call chains with weird spacing

Created on 14 Sep 2018  路  3Comments  路  Source: google/yapf

Take the following lengthy call chain

def some_function(session):
    result = (session.query(models.Employee.id).filter(models.Employee.business_id == unique_id).filter(models.Employee.age > 25).order_by(models.Employee.id.asc()).all())

I'm would love an output that looked something like this:

def some_function(session):
    result = (
        session.query(models.Employee.id)
        .filter(models.Employee.business_id == unique_id)
        .filter(models.Employee.age > 25)
        .order_by(models.Employee.id.asc())
        .all()
    )

But instead yapf is doing this:

def some_function(session):
    result = (
        session.query(models.Employee.id
                      ).filter(models.Employee.business_id == unique_id
                               ).filter(models.Employee.age > 25
                                        ).order_by(models.Employee.id.asc()
                                                   ).all()
    )

I've tried everything I can think of

  • CONTINUATION_ALIGN_STYLE
  • SPLIT_BEFORE_DOT
  • SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN
  • Adjusting the SPLIT_PENALTY_AFTER_OPENING_BRACKET value

It wouldn't be so bad if it didn't change my formatting from that second code block to the third. Right now the only thing I can think of is to either wrap the blocks with #yapf: disable tags or to use back slashes, both of which I'm pretty against.

yapf==0.24.0

Most helpful comment

:+1:

In TensorFlow's ETL API tf.data a chain of method calls is very common. It would be great to see YAPF give a nice default behavior for such fluent interfaces.

Example

Before YAPF

def create_dataset(metadata: pd.DataFrame) -> tf.data.Dataset:
    dataset = (
        tf.data.Dataset
        .from_tensor_slices(dict(metadata))
        .map(load, 256)
        .map(collate)
        .shuffle(256)
        .batch(32, drop_remainder=True)
        .prefetch(1)
    )
    return dataset

After YAPF (yapf==0.28.0 defaults)

def create_dataset(metadata: pd.DataFrame) -> tf.data.Dataset:
    dataset = (tf.data.Dataset.from_tensor_slices(dict(metadata)).map(
        load,
        256).map(collate).shuffle(256).batch(32,
                                             drop_remainder=True).prefetch(1))
    return dataset

All 3 comments

I tend to work around this by putting in fake comments:

a = (  #
    session.query(...)  #
    .filter(...)  #
    .filter(...)  #
    ...
)

:+1:

In TensorFlow's ETL API tf.data a chain of method calls is very common. It would be great to see YAPF give a nice default behavior for such fluent interfaces.

Example

Before YAPF

def create_dataset(metadata: pd.DataFrame) -> tf.data.Dataset:
    dataset = (
        tf.data.Dataset
        .from_tensor_slices(dict(metadata))
        .map(load, 256)
        .map(collate)
        .shuffle(256)
        .batch(32, drop_remainder=True)
        .prefetch(1)
    )
    return dataset

After YAPF (yapf==0.28.0 defaults)

def create_dataset(metadata: pd.DataFrame) -> tf.data.Dataset:
    dataset = (tf.data.Dataset.from_tensor_slices(dict(metadata)).map(
        load,
        256).map(collate).shuffle(256).batch(32,
                                             drop_remainder=True).prefetch(1))
    return dataset

It is possible to write chains like this to prevent yapf from changing the indentation:

def some_function(session):
    result = session.query(models.Employee.id) \
        .filter(models.Employee.business_id == unique_id) \
        .filter(models.Employee.age > 25) \
        .order_by(models.Employee.id.asc()) \
        .all()

Similar to the funny usage of empty comments above.
(Note that the space before the \ is not necessary at all, I just find it more readable.)

However, I still prefer the way the op asked in the first place, because it's easier to arrange and in some cases, you could put a call chain as an argument to another function call.

session.query(
    session.query(models.Employee.id)
    .filter(models.Employee.business_id == unique_id)
    .filter(models.Employee.age > 25)
    .order_by(models.Employee.id.asc())
    .exists()
).scalar()

In this case, the backslash trick from above wouldn't be allowed, (contrary to the empty comment trick), and yapf will reformat like this:

session.query(
    session.query(models.Employee.id).filter(models.Employee.business_id == unique_id
                                             ).filter(models.Employee.age > 25
                                                      ).order_by(models.Employee.id.asc()).exists()
).scalar()

I know black deals with this syntax "properly", but it's terribly off on many other things, so I doubt i'll ever use it.
Anyone knows if it's possible that Yapf tackles this issue? I would like to start using a formatter.

Was this page helpful?
0 / 5 - 0 ratings